@agenticmail/core 0.5.55 → 0.5.56
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/README.md +2 -1
- package/dist/index.cjs +187 -13
- package/dist/index.d.cts +23 -1
- package/dist/index.d.ts +23 -1
- package/dist/index.js +186 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ Every other AgenticMail package depends on this one.
|
|
|
28
28
|
|
|
29
29
|
## What This Package Does
|
|
30
30
|
|
|
31
|
-
AgenticMail Core provides
|
|
31
|
+
AgenticMail Core provides 16 major components organized into modules:
|
|
32
32
|
|
|
33
33
|
### Agent Management
|
|
34
34
|
- **AccountManager** — Creates, lists, finds, and deletes AI agent accounts. Each agent gets their own email address, login credentials, and a unique API key. Agent names must be email-safe (letters, numbers, dots, hyphens, underscores only).
|
|
@@ -58,6 +58,7 @@ AgenticMail Core provides 15 major components organized into modules:
|
|
|
58
58
|
- **scanOutboundEmail** — Scans every outgoing email before it's sent, looking for sensitive data that an AI agent shouldn't be leaking. Detects API keys (AWS, OpenAI, GitHub, Stripe, and many more), passwords, private keys (SSH, PGP, RSA), personally identifiable information (Social Security numbers, credit card numbers, bank account numbers, passport numbers, dates of birth, driver's licenses), database connection strings, JWT tokens, cryptocurrency wallet addresses, webhook URLs, environment variable blocks, and more. Also checks attachment filenames for risky file types. If any high-severity match is found, the email is blocked. Emails between local agents (`@localhost` recipients) skip scanning entirely.
|
|
59
59
|
- **sanitizeEmail** — Cleans up incoming email HTML to remove hidden content that could be used for prompt injection or phishing. Strips invisible Unicode characters (tag characters, zero-width joiners, bidirectional controls, soft hyphens), removes hidden HTML elements (display:none, visibility:hidden, font-size:0, white-on-white text, off-screen positioned elements, hidden iframes), removes script tags, strips data: and javascript: URIs, and removes suspicious HTML comments that contain words like "ignore", "system", "instruction", or "prompt". Returns both the cleaned content and a list of everything it found and removed.
|
|
60
60
|
- **scoreEmail** — Scores incoming email for spam and threat indicators using 47 pattern-matching rules across 9 categories. Returns a numeric score (0-100), whether it's classified as spam (score 40+) or a warning (score 20-39), the top threat category, and a list of every rule that matched with its score contribution.
|
|
61
|
+
- **classifyEmailRoute** — Assigns incoming mail a route class such as `ignore_spam`, `ignore_newsletter`, `archive_automated`, `project_update`, `deal_escalation`, or `agent_instruction`. The classification includes the suggested action, confidence, reason, and whether a human gate is required before downstream action.
|
|
61
62
|
- **isInternalEmail** — Detects whether an email is from another local agent (agent-to-agent communication on `@localhost`). Importantly, it recognizes relay emails — if the "from" address is `@localhost` but the reply-to address is external, it's a forwarded relay email and should be treated as external, not internal.
|
|
62
63
|
- **buildInboundSecurityAdvisory** — Analyzes incoming email attachments and spam matches to build a structured security advisory with risk levels (critical, high, medium) for attachments, double-extension detection (like `invoice.pdf.exe`), and link warnings.
|
|
63
64
|
|
package/dist/index.cjs
CHANGED
|
@@ -737,6 +737,7 @@ __export(index_exports, {
|
|
|
737
737
|
TunnelManager: () => TunnelManager,
|
|
738
738
|
WARNING_THRESHOLD: () => WARNING_THRESHOLD,
|
|
739
739
|
buildInboundSecurityAdvisory: () => buildInboundSecurityAdvisory,
|
|
740
|
+
classifyEmailRoute: () => classifyEmailRoute,
|
|
740
741
|
closeDatabase: () => closeDatabase,
|
|
741
742
|
createTestDatabase: () => createTestDatabase,
|
|
742
743
|
debug: () => debug,
|
|
@@ -2240,6 +2241,178 @@ var AgentDeletionService = class {
|
|
|
2240
2241
|
// src/index.ts
|
|
2241
2242
|
init_spam_filter();
|
|
2242
2243
|
|
|
2244
|
+
// src/mail/route-classifier.ts
|
|
2245
|
+
var DEAL_TERMS = [
|
|
2246
|
+
"contract",
|
|
2247
|
+
"proposal",
|
|
2248
|
+
"quote",
|
|
2249
|
+
"pricing",
|
|
2250
|
+
"price",
|
|
2251
|
+
"budget",
|
|
2252
|
+
"purchase order",
|
|
2253
|
+
"invoice",
|
|
2254
|
+
"deal",
|
|
2255
|
+
"renewal",
|
|
2256
|
+
"msa",
|
|
2257
|
+
"sow",
|
|
2258
|
+
"deadline",
|
|
2259
|
+
"urgent",
|
|
2260
|
+
"asap",
|
|
2261
|
+
"time sensitive"
|
|
2262
|
+
];
|
|
2263
|
+
var INSTRUCTION_TERMS = [
|
|
2264
|
+
"task",
|
|
2265
|
+
"instruction",
|
|
2266
|
+
"please",
|
|
2267
|
+
"can you",
|
|
2268
|
+
"could you",
|
|
2269
|
+
"follow up",
|
|
2270
|
+
"draft",
|
|
2271
|
+
"reply",
|
|
2272
|
+
"send",
|
|
2273
|
+
"research",
|
|
2274
|
+
"summarize",
|
|
2275
|
+
"investigate",
|
|
2276
|
+
"action item",
|
|
2277
|
+
"todo"
|
|
2278
|
+
];
|
|
2279
|
+
var AUTOMATION_SUBJECT_TERMS = [
|
|
2280
|
+
"receipt",
|
|
2281
|
+
"notification",
|
|
2282
|
+
"alert",
|
|
2283
|
+
"build",
|
|
2284
|
+
"deployment",
|
|
2285
|
+
"backup",
|
|
2286
|
+
"statement",
|
|
2287
|
+
"verification code",
|
|
2288
|
+
"security code",
|
|
2289
|
+
"login code"
|
|
2290
|
+
];
|
|
2291
|
+
function normalize(value) {
|
|
2292
|
+
return (value ?? "").toLowerCase();
|
|
2293
|
+
}
|
|
2294
|
+
function textFor(email) {
|
|
2295
|
+
return `${email.subject ?? ""}
|
|
2296
|
+
${email.text ?? ""}
|
|
2297
|
+
${email.html ?? ""}`.toLowerCase();
|
|
2298
|
+
}
|
|
2299
|
+
function firstAddress(email) {
|
|
2300
|
+
return normalize(email.from[0]?.address);
|
|
2301
|
+
}
|
|
2302
|
+
function header(email, name) {
|
|
2303
|
+
const wanted = name.toLowerCase();
|
|
2304
|
+
for (const [key, value] of email.headers) {
|
|
2305
|
+
if (key.toLowerCase() === wanted) return normalize(value);
|
|
2306
|
+
}
|
|
2307
|
+
return "";
|
|
2308
|
+
}
|
|
2309
|
+
function localPart(address) {
|
|
2310
|
+
return address.split("@")[0] ?? "";
|
|
2311
|
+
}
|
|
2312
|
+
function containsAny(text, terms) {
|
|
2313
|
+
return terms.some((term) => text.includes(term));
|
|
2314
|
+
}
|
|
2315
|
+
function accountPolicy(account) {
|
|
2316
|
+
const metadata = account?.metadata ?? {};
|
|
2317
|
+
const value = metadata.emailRoutePolicy ?? metadata.routePolicy ?? metadata.mailboxPolicy;
|
|
2318
|
+
return typeof value === "string" ? value.toLowerCase() : "";
|
|
2319
|
+
}
|
|
2320
|
+
function isInternalAddress(address) {
|
|
2321
|
+
return address.endsWith("@localhost");
|
|
2322
|
+
}
|
|
2323
|
+
function isNewsletter(email) {
|
|
2324
|
+
const from = firstAddress(email);
|
|
2325
|
+
const subjectAndBody = textFor(email);
|
|
2326
|
+
return Boolean(
|
|
2327
|
+
header(email, "list-unsubscribe") || header(email, "list-id") || header(email, "x-campaign-id") || header(email, "x-mailchimp-campaign") || header(email, "precedence") === "list" || localPart(from).includes("newsletter") || subjectAndBody.includes("unsubscribe") || subjectAndBody.includes("newsletter") || subjectAndBody.includes("weekly digest")
|
|
2328
|
+
);
|
|
2329
|
+
}
|
|
2330
|
+
function isAutomated(email) {
|
|
2331
|
+
const from = firstAddress(email);
|
|
2332
|
+
const subject = normalize(email.subject);
|
|
2333
|
+
const precedence = header(email, "precedence");
|
|
2334
|
+
const autoSubmitted = header(email, "auto-submitted");
|
|
2335
|
+
return Boolean(
|
|
2336
|
+
autoSubmitted && autoSubmitted !== "no" || precedence === "bulk" || precedence === "auto" || localPart(from).includes("no-reply") || localPart(from).includes("noreply") || localPart(from).includes("donotreply") || containsAny(subject, AUTOMATION_SUBJECT_TERMS)
|
|
2337
|
+
);
|
|
2338
|
+
}
|
|
2339
|
+
function classifyEmailRoute(input) {
|
|
2340
|
+
const { email, spam, account } = input;
|
|
2341
|
+
const policy = accountPolicy(account);
|
|
2342
|
+
const from = firstAddress(email);
|
|
2343
|
+
const allText = textFor(email);
|
|
2344
|
+
if (spam?.isSpam) {
|
|
2345
|
+
return {
|
|
2346
|
+
routeClass: "ignore_spam",
|
|
2347
|
+
action: "ignore",
|
|
2348
|
+
gateRequired: false,
|
|
2349
|
+
confidence: "high",
|
|
2350
|
+
reason: `Spam score ${spam.score} exceeded the spam threshold`
|
|
2351
|
+
};
|
|
2352
|
+
}
|
|
2353
|
+
if (policy === "human" || policy === "private") {
|
|
2354
|
+
return {
|
|
2355
|
+
routeClass: "human_private",
|
|
2356
|
+
action: "notify",
|
|
2357
|
+
gateRequired: true,
|
|
2358
|
+
confidence: "high",
|
|
2359
|
+
reason: "Account policy marks this mailbox as human/private"
|
|
2360
|
+
};
|
|
2361
|
+
}
|
|
2362
|
+
if (isNewsletter(email)) {
|
|
2363
|
+
return {
|
|
2364
|
+
routeClass: "ignore_newsletter",
|
|
2365
|
+
action: "ignore",
|
|
2366
|
+
gateRequired: false,
|
|
2367
|
+
confidence: "high",
|
|
2368
|
+
reason: "Newsletter headers or unsubscribe signals were detected"
|
|
2369
|
+
};
|
|
2370
|
+
}
|
|
2371
|
+
if (isAutomated(email) && !containsAny(allText, DEAL_TERMS)) {
|
|
2372
|
+
return {
|
|
2373
|
+
routeClass: "archive_automated",
|
|
2374
|
+
action: "archive",
|
|
2375
|
+
gateRequired: false,
|
|
2376
|
+
confidence: "medium",
|
|
2377
|
+
reason: "Automated sender or notification pattern detected"
|
|
2378
|
+
};
|
|
2379
|
+
}
|
|
2380
|
+
if ((policy === "agent" || isInternalAddress(from)) && containsAny(allText, INSTRUCTION_TERMS)) {
|
|
2381
|
+
return {
|
|
2382
|
+
routeClass: "agent_instruction",
|
|
2383
|
+
action: "create_task",
|
|
2384
|
+
gateRequired: true,
|
|
2385
|
+
confidence: isInternalAddress(from) ? "high" : "medium",
|
|
2386
|
+
reason: "Instruction-like content for an agent mailbox was detected"
|
|
2387
|
+
};
|
|
2388
|
+
}
|
|
2389
|
+
if (containsAny(allText, DEAL_TERMS)) {
|
|
2390
|
+
return {
|
|
2391
|
+
routeClass: "deal_escalation",
|
|
2392
|
+
action: "escalate",
|
|
2393
|
+
gateRequired: true,
|
|
2394
|
+
confidence: "medium",
|
|
2395
|
+
reason: "Commercial, deadline, or negotiation language was detected"
|
|
2396
|
+
};
|
|
2397
|
+
}
|
|
2398
|
+
if (spam?.isWarning) {
|
|
2399
|
+
return {
|
|
2400
|
+
routeClass: "project_update",
|
|
2401
|
+
action: "notify",
|
|
2402
|
+
gateRequired: true,
|
|
2403
|
+
confidence: "low",
|
|
2404
|
+
reason: `Spam warning category ${spam.topCategory ?? "unknown"} requires cautious handling`
|
|
2405
|
+
};
|
|
2406
|
+
}
|
|
2407
|
+
return {
|
|
2408
|
+
routeClass: "project_update",
|
|
2409
|
+
action: "notify",
|
|
2410
|
+
gateRequired: false,
|
|
2411
|
+
confidence: "low",
|
|
2412
|
+
reason: "Default route for non-spam, non-automated email"
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2243
2416
|
// src/mail/sanitizer.ts
|
|
2244
2417
|
var RE_TAG_BLOCK = /[\u{E0001}-\u{E007F}]/gu;
|
|
2245
2418
|
var RE_ZERO_WIDTH = /[\u200B\u200C\u200D\uFEFF]/g;
|
|
@@ -3543,9 +3716,9 @@ var RelayGateway = class {
|
|
|
3543
3716
|
throw new Error("Relay not configured. Call setup() first.");
|
|
3544
3717
|
}
|
|
3545
3718
|
const atIdx = this.config.email.lastIndexOf("@");
|
|
3546
|
-
const
|
|
3719
|
+
const localPart2 = this.config.email.slice(0, atIdx);
|
|
3547
3720
|
const domain = this.config.email.slice(atIdx + 1);
|
|
3548
|
-
const relayFrom = `${
|
|
3721
|
+
const relayFrom = `${localPart2}+${agentName}@${domain}`;
|
|
3549
3722
|
const displayName = mail.fromName || agentName;
|
|
3550
3723
|
const mailOpts = {
|
|
3551
3724
|
from: `${displayName} <${relayFrom}>`,
|
|
@@ -3557,7 +3730,7 @@ var RelayGateway = class {
|
|
|
3557
3730
|
html: mail.html,
|
|
3558
3731
|
replyTo: relayFrom,
|
|
3559
3732
|
inReplyTo: mail.inReplyTo,
|
|
3560
|
-
references: mail.references
|
|
3733
|
+
references: Array.isArray(mail.references) ? mail.references.join(" ") : mail.references,
|
|
3561
3734
|
headers: mail.headers,
|
|
3562
3735
|
attachments: mail.attachments?.map((a) => ({
|
|
3563
3736
|
filename: a.filename,
|
|
@@ -3781,9 +3954,9 @@ var RelayGateway = class {
|
|
|
3781
3954
|
isOurRelaySender(address) {
|
|
3782
3955
|
if (!this.config) return false;
|
|
3783
3956
|
const atIdx = this.config.email.lastIndexOf("@");
|
|
3784
|
-
const
|
|
3957
|
+
const localPart2 = this.config.email.slice(0, atIdx);
|
|
3785
3958
|
const domain = this.config.email.slice(atIdx + 1);
|
|
3786
|
-
const pattern = new RegExp(`^${escapeRegex(
|
|
3959
|
+
const pattern = new RegExp(`^${escapeRegex(localPart2)}\\+[^@]+@${escapeRegex(domain)}$`, "i");
|
|
3787
3960
|
return pattern.test(address);
|
|
3788
3961
|
}
|
|
3789
3962
|
/**
|
|
@@ -3823,8 +3996,8 @@ var RelayGateway = class {
|
|
|
3823
3996
|
const match = addr.match(/^([^+]+)\+([^@]+)@/);
|
|
3824
3997
|
if (match && this.config) {
|
|
3825
3998
|
const atIdx = this.config.email.lastIndexOf("@");
|
|
3826
|
-
const
|
|
3827
|
-
if (match[1].toLowerCase() ===
|
|
3999
|
+
const localPart2 = this.config.email.slice(0, atIdx);
|
|
4000
|
+
if (match[1].toLowerCase() === localPart2.toLowerCase()) {
|
|
3828
4001
|
return match[2];
|
|
3829
4002
|
}
|
|
3830
4003
|
}
|
|
@@ -4365,9 +4538,9 @@ var DNSConfigurator = class {
|
|
|
4365
4538
|
const records = [];
|
|
4366
4539
|
const removed = [];
|
|
4367
4540
|
const existing = await this.cf.listDnsRecords(zoneId);
|
|
4368
|
-
const
|
|
4541
|
+
const normalize2 = (s) => s.replace(/^["']|["']$/g, "");
|
|
4369
4542
|
const findRecords = (type, name, contentPrefix) => existing.filter(
|
|
4370
|
-
(r) => r.type === type && r.name === name && (!contentPrefix ||
|
|
4543
|
+
(r) => r.type === type && r.name === name && (!contentPrefix || normalize2(r.content ?? "").startsWith(contentPrefix))
|
|
4371
4544
|
);
|
|
4372
4545
|
const existingMx = findRecords("MX", domain);
|
|
4373
4546
|
const cfEmailRoutingMx = existingMx.filter((r) => (r.content ?? "").startsWith("_dc-mx."));
|
|
@@ -4391,7 +4564,7 @@ var DNSConfigurator = class {
|
|
|
4391
4564
|
const ipClause = serverIp ? `ip4:${serverIp} ` : "";
|
|
4392
4565
|
const ourSpf = `v=spf1 ${ipClause}include:_spf.mx.cloudflare.net mx ~all`;
|
|
4393
4566
|
const existingSpf = findRecords("TXT", domain, "v=spf1");
|
|
4394
|
-
const alreadyHasOurSpf = existingSpf.some((r) =>
|
|
4567
|
+
const alreadyHasOurSpf = existingSpf.some((r) => normalize2(r.content) === ourSpf);
|
|
4395
4568
|
if (!alreadyHasOurSpf) {
|
|
4396
4569
|
for (const spf of existingSpf) {
|
|
4397
4570
|
await this.cf.deleteDnsRecord(zoneId, spf.id);
|
|
@@ -4422,7 +4595,7 @@ var DNSConfigurator = class {
|
|
|
4422
4595
|
const dkimName = `${options.dkimSelector}._domainkey.${domain}`;
|
|
4423
4596
|
const ourDkim = `v=DKIM1; k=rsa; p=${options.dkimPublicKey}`;
|
|
4424
4597
|
const existingDkim = findRecords("TXT", dkimName, "v=DKIM1");
|
|
4425
|
-
const alreadyCorrect = existingDkim.some((r) =>
|
|
4598
|
+
const alreadyCorrect = existingDkim.some((r) => normalize2(r.content) === ourDkim);
|
|
4426
4599
|
if (!alreadyCorrect) {
|
|
4427
4600
|
for (const rec of existingDkim) {
|
|
4428
4601
|
await this.cf.deleteDnsRecord(zoneId, rec.id);
|
|
@@ -5264,7 +5437,7 @@ var GatewayManager = class {
|
|
|
5264
5437
|
html: mail.html || void 0,
|
|
5265
5438
|
replyTo: mail.from,
|
|
5266
5439
|
inReplyTo: mail.inReplyTo,
|
|
5267
|
-
references: mail.references
|
|
5440
|
+
references: Array.isArray(mail.references) ? mail.references.join(" ") : mail.references,
|
|
5268
5441
|
headers: {
|
|
5269
5442
|
"X-AgenticMail-Relay": "inbound",
|
|
5270
5443
|
"X-Original-From": mail.from,
|
|
@@ -5753,7 +5926,7 @@ var GatewayManager = class {
|
|
|
5753
5926
|
html: mail.html || void 0,
|
|
5754
5927
|
replyTo: mail.replyTo || from,
|
|
5755
5928
|
inReplyTo: mail.inReplyTo || void 0,
|
|
5756
|
-
references: mail.references
|
|
5929
|
+
references: Array.isArray(mail.references) ? mail.references.join(" ") : mail.references || void 0,
|
|
5757
5930
|
headers: {
|
|
5758
5931
|
"X-Mailer": "AgenticMail/1.0"
|
|
5759
5932
|
},
|
|
@@ -7535,6 +7708,7 @@ secret = "${password}"
|
|
|
7535
7708
|
TunnelManager,
|
|
7536
7709
|
WARNING_THRESHOLD,
|
|
7537
7710
|
buildInboundSecurityAdvisory,
|
|
7711
|
+
classifyEmailRoute,
|
|
7538
7712
|
closeDatabase,
|
|
7539
7713
|
createTestDatabase,
|
|
7540
7714
|
debug,
|
package/dist/index.d.cts
CHANGED
|
@@ -507,6 +507,28 @@ declare const WARNING_THRESHOLD = 20;
|
|
|
507
507
|
declare function isInternalEmail(email: ParsedEmail, localDomains?: string[]): boolean;
|
|
508
508
|
declare function scoreEmail(email: ParsedEmail): SpamResult;
|
|
509
509
|
|
|
510
|
+
type EmailRouteClass = 'ignore_spam' | 'ignore_newsletter' | 'archive_automated' | 'project_update' | 'deal_escalation' | 'agent_instruction' | 'human_private';
|
|
511
|
+
type EmailRouteAction = 'ignore' | 'archive' | 'notify' | 'escalate' | 'create_task' | 'draft_reply';
|
|
512
|
+
interface EmailRouteAccountContext {
|
|
513
|
+
name?: string;
|
|
514
|
+
email?: string;
|
|
515
|
+
role?: string;
|
|
516
|
+
metadata?: Record<string, unknown>;
|
|
517
|
+
}
|
|
518
|
+
interface EmailRouteInput {
|
|
519
|
+
email: ParsedEmail;
|
|
520
|
+
spam?: Pick<SpamResult, 'score' | 'isSpam' | 'isWarning' | 'topCategory'>;
|
|
521
|
+
account?: EmailRouteAccountContext;
|
|
522
|
+
}
|
|
523
|
+
interface EmailRouteClassification {
|
|
524
|
+
routeClass: EmailRouteClass;
|
|
525
|
+
action: EmailRouteAction;
|
|
526
|
+
gateRequired: boolean;
|
|
527
|
+
confidence: 'low' | 'medium' | 'high';
|
|
528
|
+
reason: string;
|
|
529
|
+
}
|
|
530
|
+
declare function classifyEmailRoute(input: EmailRouteInput): EmailRouteClassification;
|
|
531
|
+
|
|
510
532
|
interface SanitizeDetection {
|
|
511
533
|
type: string;
|
|
512
534
|
description: string;
|
|
@@ -1673,4 +1695,4 @@ declare class SetupManager {
|
|
|
1673
1695
|
isInitialized(): boolean;
|
|
1674
1696
|
}
|
|
1675
1697
|
|
|
1676
|
-
export { AGENT_ROLES, AccountManager, type AddressInfo, type Agent, AgentDeletionService, type AgentRole, AgenticMailClient, type AgenticMailClientOptions, type AgenticMailConfig, type ArchiveAndDeleteOptions, type ArchivedEmail, type Attachment, type AttachmentAdvisory, CloudflareClient, type CreateAgentOptions, DEFAULT_AGENT_NAME, DEFAULT_AGENT_ROLE, DNSConfigurator, type DeletionReport, type DeletionSummary, DependencyChecker, DependencyInstaller, type DependencyStatus, type DnsRecord, type DnsSetupResult, type DomainInfo, DomainManager, type DomainModeConfig, type DomainPurchaseResult, DomainPurchaser, type DomainSearchResult, type DomainSetupResult, type EmailEnvelope, EmailSearchIndex, type FolderInfo, type GatewayConfig, GatewayManager, type GatewayManagerOptions, type GatewayMode, type GatewayStatus, type InboundEmail, type InboxEvent, type InboxExpungeEvent, type InboxFlagsEvent, type InboxNewEvent, InboxWatcher, type InboxWatcherOptions, type InstallProgress, type LinkAdvisory, type LocalSmtpConfig, MailReceiver, type MailReceiverOptions, MailSender, type MailSenderOptions, type MailboxInfo, type OutboundCategory, type OutboundScanInput, type OutboundScanResult, type OutboundWarning, type ParsedAttachment, type ParsedEmail, type ParsedSms, type PurchasedDomain, RELAY_PRESETS, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, SPAM_THRESHOLD, type SanitizeDetection, type SanitizeResult, type SearchCriteria, type SearchableEmail, type SecurityAdvisory, type SendMailOptions, type SendResult, type SendResultWithRaw, ServiceManager, type ServiceStatus, type SetupConfig, SetupManager, type SetupResult, type Severity, type SmsConfig, SmsManager, type SmsMessage, SmsPoller, type SpamCategory, type SpamResult, type SpamRuleMatch, StalwartAdmin, type StalwartAdminOptions, type StalwartPrincipal, type TunnelConfig, TunnelManager, WARNING_THRESHOLD, type WatcherOptions, buildInboundSecurityAdvisory, closeDatabase, createTestDatabase, debug, debugWarn, ensureDataDir, extractVerificationCode, flushTelemetry, getDatabase, isInternalEmail, isValidPhoneNumber, normalizePhoneNumber, parseEmail, parseGoogleVoiceSms, recordToolCall, resolveConfig, sanitizeEmail, saveConfig, scanOutboundEmail, scoreEmail, setTelemetryVersion, startRelayBridge };
|
|
1698
|
+
export { AGENT_ROLES, AccountManager, type AddressInfo, type Agent, AgentDeletionService, type AgentRole, AgenticMailClient, type AgenticMailClientOptions, type AgenticMailConfig, type ArchiveAndDeleteOptions, type ArchivedEmail, type Attachment, type AttachmentAdvisory, CloudflareClient, type CreateAgentOptions, DEFAULT_AGENT_NAME, DEFAULT_AGENT_ROLE, DNSConfigurator, type DeletionReport, type DeletionSummary, DependencyChecker, DependencyInstaller, type DependencyStatus, type DnsRecord, type DnsSetupResult, type DomainInfo, DomainManager, type DomainModeConfig, type DomainPurchaseResult, DomainPurchaser, type DomainSearchResult, type DomainSetupResult, type EmailEnvelope, type EmailRouteAction, type EmailRouteClass, type EmailRouteClassification, type EmailRouteInput, EmailSearchIndex, type FolderInfo, type GatewayConfig, GatewayManager, type GatewayManagerOptions, type GatewayMode, type GatewayStatus, type InboundEmail, type InboxEvent, type InboxExpungeEvent, type InboxFlagsEvent, type InboxNewEvent, InboxWatcher, type InboxWatcherOptions, type InstallProgress, type LinkAdvisory, type LocalSmtpConfig, MailReceiver, type MailReceiverOptions, MailSender, type MailSenderOptions, type MailboxInfo, type OutboundCategory, type OutboundScanInput, type OutboundScanResult, type OutboundWarning, type ParsedAttachment, type ParsedEmail, type ParsedSms, type PurchasedDomain, RELAY_PRESETS, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, SPAM_THRESHOLD, type SanitizeDetection, type SanitizeResult, type SearchCriteria, type SearchableEmail, type SecurityAdvisory, type SendMailOptions, type SendResult, type SendResultWithRaw, ServiceManager, type ServiceStatus, type SetupConfig, SetupManager, type SetupResult, type Severity, type SmsConfig, SmsManager, type SmsMessage, SmsPoller, type SpamCategory, type SpamResult, type SpamRuleMatch, StalwartAdmin, type StalwartAdminOptions, type StalwartPrincipal, type TunnelConfig, TunnelManager, WARNING_THRESHOLD, type WatcherOptions, buildInboundSecurityAdvisory, classifyEmailRoute, closeDatabase, createTestDatabase, debug, debugWarn, ensureDataDir, extractVerificationCode, flushTelemetry, getDatabase, isInternalEmail, isValidPhoneNumber, normalizePhoneNumber, parseEmail, parseGoogleVoiceSms, recordToolCall, resolveConfig, sanitizeEmail, saveConfig, scanOutboundEmail, scoreEmail, setTelemetryVersion, startRelayBridge };
|
package/dist/index.d.ts
CHANGED
|
@@ -507,6 +507,28 @@ declare const WARNING_THRESHOLD = 20;
|
|
|
507
507
|
declare function isInternalEmail(email: ParsedEmail, localDomains?: string[]): boolean;
|
|
508
508
|
declare function scoreEmail(email: ParsedEmail): SpamResult;
|
|
509
509
|
|
|
510
|
+
type EmailRouteClass = 'ignore_spam' | 'ignore_newsletter' | 'archive_automated' | 'project_update' | 'deal_escalation' | 'agent_instruction' | 'human_private';
|
|
511
|
+
type EmailRouteAction = 'ignore' | 'archive' | 'notify' | 'escalate' | 'create_task' | 'draft_reply';
|
|
512
|
+
interface EmailRouteAccountContext {
|
|
513
|
+
name?: string;
|
|
514
|
+
email?: string;
|
|
515
|
+
role?: string;
|
|
516
|
+
metadata?: Record<string, unknown>;
|
|
517
|
+
}
|
|
518
|
+
interface EmailRouteInput {
|
|
519
|
+
email: ParsedEmail;
|
|
520
|
+
spam?: Pick<SpamResult, 'score' | 'isSpam' | 'isWarning' | 'topCategory'>;
|
|
521
|
+
account?: EmailRouteAccountContext;
|
|
522
|
+
}
|
|
523
|
+
interface EmailRouteClassification {
|
|
524
|
+
routeClass: EmailRouteClass;
|
|
525
|
+
action: EmailRouteAction;
|
|
526
|
+
gateRequired: boolean;
|
|
527
|
+
confidence: 'low' | 'medium' | 'high';
|
|
528
|
+
reason: string;
|
|
529
|
+
}
|
|
530
|
+
declare function classifyEmailRoute(input: EmailRouteInput): EmailRouteClassification;
|
|
531
|
+
|
|
510
532
|
interface SanitizeDetection {
|
|
511
533
|
type: string;
|
|
512
534
|
description: string;
|
|
@@ -1673,4 +1695,4 @@ declare class SetupManager {
|
|
|
1673
1695
|
isInitialized(): boolean;
|
|
1674
1696
|
}
|
|
1675
1697
|
|
|
1676
|
-
export { AGENT_ROLES, AccountManager, type AddressInfo, type Agent, AgentDeletionService, type AgentRole, AgenticMailClient, type AgenticMailClientOptions, type AgenticMailConfig, type ArchiveAndDeleteOptions, type ArchivedEmail, type Attachment, type AttachmentAdvisory, CloudflareClient, type CreateAgentOptions, DEFAULT_AGENT_NAME, DEFAULT_AGENT_ROLE, DNSConfigurator, type DeletionReport, type DeletionSummary, DependencyChecker, DependencyInstaller, type DependencyStatus, type DnsRecord, type DnsSetupResult, type DomainInfo, DomainManager, type DomainModeConfig, type DomainPurchaseResult, DomainPurchaser, type DomainSearchResult, type DomainSetupResult, type EmailEnvelope, EmailSearchIndex, type FolderInfo, type GatewayConfig, GatewayManager, type GatewayManagerOptions, type GatewayMode, type GatewayStatus, type InboundEmail, type InboxEvent, type InboxExpungeEvent, type InboxFlagsEvent, type InboxNewEvent, InboxWatcher, type InboxWatcherOptions, type InstallProgress, type LinkAdvisory, type LocalSmtpConfig, MailReceiver, type MailReceiverOptions, MailSender, type MailSenderOptions, type MailboxInfo, type OutboundCategory, type OutboundScanInput, type OutboundScanResult, type OutboundWarning, type ParsedAttachment, type ParsedEmail, type ParsedSms, type PurchasedDomain, RELAY_PRESETS, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, SPAM_THRESHOLD, type SanitizeDetection, type SanitizeResult, type SearchCriteria, type SearchableEmail, type SecurityAdvisory, type SendMailOptions, type SendResult, type SendResultWithRaw, ServiceManager, type ServiceStatus, type SetupConfig, SetupManager, type SetupResult, type Severity, type SmsConfig, SmsManager, type SmsMessage, SmsPoller, type SpamCategory, type SpamResult, type SpamRuleMatch, StalwartAdmin, type StalwartAdminOptions, type StalwartPrincipal, type TunnelConfig, TunnelManager, WARNING_THRESHOLD, type WatcherOptions, buildInboundSecurityAdvisory, closeDatabase, createTestDatabase, debug, debugWarn, ensureDataDir, extractVerificationCode, flushTelemetry, getDatabase, isInternalEmail, isValidPhoneNumber, normalizePhoneNumber, parseEmail, parseGoogleVoiceSms, recordToolCall, resolveConfig, sanitizeEmail, saveConfig, scanOutboundEmail, scoreEmail, setTelemetryVersion, startRelayBridge };
|
|
1698
|
+
export { AGENT_ROLES, AccountManager, type AddressInfo, type Agent, AgentDeletionService, type AgentRole, AgenticMailClient, type AgenticMailClientOptions, type AgenticMailConfig, type ArchiveAndDeleteOptions, type ArchivedEmail, type Attachment, type AttachmentAdvisory, CloudflareClient, type CreateAgentOptions, DEFAULT_AGENT_NAME, DEFAULT_AGENT_ROLE, DNSConfigurator, type DeletionReport, type DeletionSummary, DependencyChecker, DependencyInstaller, type DependencyStatus, type DnsRecord, type DnsSetupResult, type DomainInfo, DomainManager, type DomainModeConfig, type DomainPurchaseResult, DomainPurchaser, type DomainSearchResult, type DomainSetupResult, type EmailEnvelope, type EmailRouteAction, type EmailRouteClass, type EmailRouteClassification, type EmailRouteInput, EmailSearchIndex, type FolderInfo, type GatewayConfig, GatewayManager, type GatewayManagerOptions, type GatewayMode, type GatewayStatus, type InboundEmail, type InboxEvent, type InboxExpungeEvent, type InboxFlagsEvent, type InboxNewEvent, InboxWatcher, type InboxWatcherOptions, type InstallProgress, type LinkAdvisory, type LocalSmtpConfig, MailReceiver, type MailReceiverOptions, MailSender, type MailSenderOptions, type MailboxInfo, type OutboundCategory, type OutboundScanInput, type OutboundScanResult, type OutboundWarning, type ParsedAttachment, type ParsedEmail, type ParsedSms, type PurchasedDomain, RELAY_PRESETS, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, SPAM_THRESHOLD, type SanitizeDetection, type SanitizeResult, type SearchCriteria, type SearchableEmail, type SecurityAdvisory, type SendMailOptions, type SendResult, type SendResultWithRaw, ServiceManager, type ServiceStatus, type SetupConfig, SetupManager, type SetupResult, type Severity, type SmsConfig, SmsManager, type SmsMessage, SmsPoller, type SpamCategory, type SpamResult, type SpamRuleMatch, StalwartAdmin, type StalwartAdminOptions, type StalwartPrincipal, type TunnelConfig, TunnelManager, WARNING_THRESHOLD, type WatcherOptions, buildInboundSecurityAdvisory, classifyEmailRoute, closeDatabase, createTestDatabase, debug, debugWarn, ensureDataDir, extractVerificationCode, flushTelemetry, getDatabase, isInternalEmail, isValidPhoneNumber, normalizePhoneNumber, parseEmail, parseGoogleVoiceSms, recordToolCall, resolveConfig, sanitizeEmail, saveConfig, scanOutboundEmail, scoreEmail, setTelemetryVersion, startRelayBridge };
|
package/dist/index.js
CHANGED
|
@@ -1484,6 +1484,178 @@ var AgentDeletionService = class {
|
|
|
1484
1484
|
}
|
|
1485
1485
|
};
|
|
1486
1486
|
|
|
1487
|
+
// src/mail/route-classifier.ts
|
|
1488
|
+
var DEAL_TERMS = [
|
|
1489
|
+
"contract",
|
|
1490
|
+
"proposal",
|
|
1491
|
+
"quote",
|
|
1492
|
+
"pricing",
|
|
1493
|
+
"price",
|
|
1494
|
+
"budget",
|
|
1495
|
+
"purchase order",
|
|
1496
|
+
"invoice",
|
|
1497
|
+
"deal",
|
|
1498
|
+
"renewal",
|
|
1499
|
+
"msa",
|
|
1500
|
+
"sow",
|
|
1501
|
+
"deadline",
|
|
1502
|
+
"urgent",
|
|
1503
|
+
"asap",
|
|
1504
|
+
"time sensitive"
|
|
1505
|
+
];
|
|
1506
|
+
var INSTRUCTION_TERMS = [
|
|
1507
|
+
"task",
|
|
1508
|
+
"instruction",
|
|
1509
|
+
"please",
|
|
1510
|
+
"can you",
|
|
1511
|
+
"could you",
|
|
1512
|
+
"follow up",
|
|
1513
|
+
"draft",
|
|
1514
|
+
"reply",
|
|
1515
|
+
"send",
|
|
1516
|
+
"research",
|
|
1517
|
+
"summarize",
|
|
1518
|
+
"investigate",
|
|
1519
|
+
"action item",
|
|
1520
|
+
"todo"
|
|
1521
|
+
];
|
|
1522
|
+
var AUTOMATION_SUBJECT_TERMS = [
|
|
1523
|
+
"receipt",
|
|
1524
|
+
"notification",
|
|
1525
|
+
"alert",
|
|
1526
|
+
"build",
|
|
1527
|
+
"deployment",
|
|
1528
|
+
"backup",
|
|
1529
|
+
"statement",
|
|
1530
|
+
"verification code",
|
|
1531
|
+
"security code",
|
|
1532
|
+
"login code"
|
|
1533
|
+
];
|
|
1534
|
+
function normalize(value) {
|
|
1535
|
+
return (value ?? "").toLowerCase();
|
|
1536
|
+
}
|
|
1537
|
+
function textFor(email) {
|
|
1538
|
+
return `${email.subject ?? ""}
|
|
1539
|
+
${email.text ?? ""}
|
|
1540
|
+
${email.html ?? ""}`.toLowerCase();
|
|
1541
|
+
}
|
|
1542
|
+
function firstAddress(email) {
|
|
1543
|
+
return normalize(email.from[0]?.address);
|
|
1544
|
+
}
|
|
1545
|
+
function header(email, name) {
|
|
1546
|
+
const wanted = name.toLowerCase();
|
|
1547
|
+
for (const [key, value] of email.headers) {
|
|
1548
|
+
if (key.toLowerCase() === wanted) return normalize(value);
|
|
1549
|
+
}
|
|
1550
|
+
return "";
|
|
1551
|
+
}
|
|
1552
|
+
function localPart(address) {
|
|
1553
|
+
return address.split("@")[0] ?? "";
|
|
1554
|
+
}
|
|
1555
|
+
function containsAny(text, terms) {
|
|
1556
|
+
return terms.some((term) => text.includes(term));
|
|
1557
|
+
}
|
|
1558
|
+
function accountPolicy(account) {
|
|
1559
|
+
const metadata = account?.metadata ?? {};
|
|
1560
|
+
const value = metadata.emailRoutePolicy ?? metadata.routePolicy ?? metadata.mailboxPolicy;
|
|
1561
|
+
return typeof value === "string" ? value.toLowerCase() : "";
|
|
1562
|
+
}
|
|
1563
|
+
function isInternalAddress(address) {
|
|
1564
|
+
return address.endsWith("@localhost");
|
|
1565
|
+
}
|
|
1566
|
+
function isNewsletter(email) {
|
|
1567
|
+
const from = firstAddress(email);
|
|
1568
|
+
const subjectAndBody = textFor(email);
|
|
1569
|
+
return Boolean(
|
|
1570
|
+
header(email, "list-unsubscribe") || header(email, "list-id") || header(email, "x-campaign-id") || header(email, "x-mailchimp-campaign") || header(email, "precedence") === "list" || localPart(from).includes("newsletter") || subjectAndBody.includes("unsubscribe") || subjectAndBody.includes("newsletter") || subjectAndBody.includes("weekly digest")
|
|
1571
|
+
);
|
|
1572
|
+
}
|
|
1573
|
+
function isAutomated(email) {
|
|
1574
|
+
const from = firstAddress(email);
|
|
1575
|
+
const subject = normalize(email.subject);
|
|
1576
|
+
const precedence = header(email, "precedence");
|
|
1577
|
+
const autoSubmitted = header(email, "auto-submitted");
|
|
1578
|
+
return Boolean(
|
|
1579
|
+
autoSubmitted && autoSubmitted !== "no" || precedence === "bulk" || precedence === "auto" || localPart(from).includes("no-reply") || localPart(from).includes("noreply") || localPart(from).includes("donotreply") || containsAny(subject, AUTOMATION_SUBJECT_TERMS)
|
|
1580
|
+
);
|
|
1581
|
+
}
|
|
1582
|
+
function classifyEmailRoute(input) {
|
|
1583
|
+
const { email, spam, account } = input;
|
|
1584
|
+
const policy = accountPolicy(account);
|
|
1585
|
+
const from = firstAddress(email);
|
|
1586
|
+
const allText = textFor(email);
|
|
1587
|
+
if (spam?.isSpam) {
|
|
1588
|
+
return {
|
|
1589
|
+
routeClass: "ignore_spam",
|
|
1590
|
+
action: "ignore",
|
|
1591
|
+
gateRequired: false,
|
|
1592
|
+
confidence: "high",
|
|
1593
|
+
reason: `Spam score ${spam.score} exceeded the spam threshold`
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
if (policy === "human" || policy === "private") {
|
|
1597
|
+
return {
|
|
1598
|
+
routeClass: "human_private",
|
|
1599
|
+
action: "notify",
|
|
1600
|
+
gateRequired: true,
|
|
1601
|
+
confidence: "high",
|
|
1602
|
+
reason: "Account policy marks this mailbox as human/private"
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
if (isNewsletter(email)) {
|
|
1606
|
+
return {
|
|
1607
|
+
routeClass: "ignore_newsletter",
|
|
1608
|
+
action: "ignore",
|
|
1609
|
+
gateRequired: false,
|
|
1610
|
+
confidence: "high",
|
|
1611
|
+
reason: "Newsletter headers or unsubscribe signals were detected"
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
if (isAutomated(email) && !containsAny(allText, DEAL_TERMS)) {
|
|
1615
|
+
return {
|
|
1616
|
+
routeClass: "archive_automated",
|
|
1617
|
+
action: "archive",
|
|
1618
|
+
gateRequired: false,
|
|
1619
|
+
confidence: "medium",
|
|
1620
|
+
reason: "Automated sender or notification pattern detected"
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
if ((policy === "agent" || isInternalAddress(from)) && containsAny(allText, INSTRUCTION_TERMS)) {
|
|
1624
|
+
return {
|
|
1625
|
+
routeClass: "agent_instruction",
|
|
1626
|
+
action: "create_task",
|
|
1627
|
+
gateRequired: true,
|
|
1628
|
+
confidence: isInternalAddress(from) ? "high" : "medium",
|
|
1629
|
+
reason: "Instruction-like content for an agent mailbox was detected"
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
if (containsAny(allText, DEAL_TERMS)) {
|
|
1633
|
+
return {
|
|
1634
|
+
routeClass: "deal_escalation",
|
|
1635
|
+
action: "escalate",
|
|
1636
|
+
gateRequired: true,
|
|
1637
|
+
confidence: "medium",
|
|
1638
|
+
reason: "Commercial, deadline, or negotiation language was detected"
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
if (spam?.isWarning) {
|
|
1642
|
+
return {
|
|
1643
|
+
routeClass: "project_update",
|
|
1644
|
+
action: "notify",
|
|
1645
|
+
gateRequired: true,
|
|
1646
|
+
confidence: "low",
|
|
1647
|
+
reason: `Spam warning category ${spam.topCategory ?? "unknown"} requires cautious handling`
|
|
1648
|
+
};
|
|
1649
|
+
}
|
|
1650
|
+
return {
|
|
1651
|
+
routeClass: "project_update",
|
|
1652
|
+
action: "notify",
|
|
1653
|
+
gateRequired: false,
|
|
1654
|
+
confidence: "low",
|
|
1655
|
+
reason: "Default route for non-spam, non-automated email"
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1487
1659
|
// src/mail/sanitizer.ts
|
|
1488
1660
|
var RE_TAG_BLOCK = /[\u{E0001}-\u{E007F}]/gu;
|
|
1489
1661
|
var RE_ZERO_WIDTH = /[\u200B\u200C\u200D\uFEFF]/g;
|
|
@@ -2787,9 +2959,9 @@ var RelayGateway = class {
|
|
|
2787
2959
|
throw new Error("Relay not configured. Call setup() first.");
|
|
2788
2960
|
}
|
|
2789
2961
|
const atIdx = this.config.email.lastIndexOf("@");
|
|
2790
|
-
const
|
|
2962
|
+
const localPart2 = this.config.email.slice(0, atIdx);
|
|
2791
2963
|
const domain = this.config.email.slice(atIdx + 1);
|
|
2792
|
-
const relayFrom = `${
|
|
2964
|
+
const relayFrom = `${localPart2}+${agentName}@${domain}`;
|
|
2793
2965
|
const displayName = mail.fromName || agentName;
|
|
2794
2966
|
const mailOpts = {
|
|
2795
2967
|
from: `${displayName} <${relayFrom}>`,
|
|
@@ -2801,7 +2973,7 @@ var RelayGateway = class {
|
|
|
2801
2973
|
html: mail.html,
|
|
2802
2974
|
replyTo: relayFrom,
|
|
2803
2975
|
inReplyTo: mail.inReplyTo,
|
|
2804
|
-
references: mail.references
|
|
2976
|
+
references: Array.isArray(mail.references) ? mail.references.join(" ") : mail.references,
|
|
2805
2977
|
headers: mail.headers,
|
|
2806
2978
|
attachments: mail.attachments?.map((a) => ({
|
|
2807
2979
|
filename: a.filename,
|
|
@@ -3025,9 +3197,9 @@ var RelayGateway = class {
|
|
|
3025
3197
|
isOurRelaySender(address) {
|
|
3026
3198
|
if (!this.config) return false;
|
|
3027
3199
|
const atIdx = this.config.email.lastIndexOf("@");
|
|
3028
|
-
const
|
|
3200
|
+
const localPart2 = this.config.email.slice(0, atIdx);
|
|
3029
3201
|
const domain = this.config.email.slice(atIdx + 1);
|
|
3030
|
-
const pattern = new RegExp(`^${escapeRegex(
|
|
3202
|
+
const pattern = new RegExp(`^${escapeRegex(localPart2)}\\+[^@]+@${escapeRegex(domain)}$`, "i");
|
|
3031
3203
|
return pattern.test(address);
|
|
3032
3204
|
}
|
|
3033
3205
|
/**
|
|
@@ -3067,8 +3239,8 @@ var RelayGateway = class {
|
|
|
3067
3239
|
const match = addr.match(/^([^+]+)\+([^@]+)@/);
|
|
3068
3240
|
if (match && this.config) {
|
|
3069
3241
|
const atIdx = this.config.email.lastIndexOf("@");
|
|
3070
|
-
const
|
|
3071
|
-
if (match[1].toLowerCase() ===
|
|
3242
|
+
const localPart2 = this.config.email.slice(0, atIdx);
|
|
3243
|
+
if (match[1].toLowerCase() === localPart2.toLowerCase()) {
|
|
3072
3244
|
return match[2];
|
|
3073
3245
|
}
|
|
3074
3246
|
}
|
|
@@ -3609,9 +3781,9 @@ var DNSConfigurator = class {
|
|
|
3609
3781
|
const records = [];
|
|
3610
3782
|
const removed = [];
|
|
3611
3783
|
const existing = await this.cf.listDnsRecords(zoneId);
|
|
3612
|
-
const
|
|
3784
|
+
const normalize2 = (s) => s.replace(/^["']|["']$/g, "");
|
|
3613
3785
|
const findRecords = (type, name, contentPrefix) => existing.filter(
|
|
3614
|
-
(r) => r.type === type && r.name === name && (!contentPrefix ||
|
|
3786
|
+
(r) => r.type === type && r.name === name && (!contentPrefix || normalize2(r.content ?? "").startsWith(contentPrefix))
|
|
3615
3787
|
);
|
|
3616
3788
|
const existingMx = findRecords("MX", domain);
|
|
3617
3789
|
const cfEmailRoutingMx = existingMx.filter((r) => (r.content ?? "").startsWith("_dc-mx."));
|
|
@@ -3635,7 +3807,7 @@ var DNSConfigurator = class {
|
|
|
3635
3807
|
const ipClause = serverIp ? `ip4:${serverIp} ` : "";
|
|
3636
3808
|
const ourSpf = `v=spf1 ${ipClause}include:_spf.mx.cloudflare.net mx ~all`;
|
|
3637
3809
|
const existingSpf = findRecords("TXT", domain, "v=spf1");
|
|
3638
|
-
const alreadyHasOurSpf = existingSpf.some((r) =>
|
|
3810
|
+
const alreadyHasOurSpf = existingSpf.some((r) => normalize2(r.content) === ourSpf);
|
|
3639
3811
|
if (!alreadyHasOurSpf) {
|
|
3640
3812
|
for (const spf of existingSpf) {
|
|
3641
3813
|
await this.cf.deleteDnsRecord(zoneId, spf.id);
|
|
@@ -3666,7 +3838,7 @@ var DNSConfigurator = class {
|
|
|
3666
3838
|
const dkimName = `${options.dkimSelector}._domainkey.${domain}`;
|
|
3667
3839
|
const ourDkim = `v=DKIM1; k=rsa; p=${options.dkimPublicKey}`;
|
|
3668
3840
|
const existingDkim = findRecords("TXT", dkimName, "v=DKIM1");
|
|
3669
|
-
const alreadyCorrect = existingDkim.some((r) =>
|
|
3841
|
+
const alreadyCorrect = existingDkim.some((r) => normalize2(r.content) === ourDkim);
|
|
3670
3842
|
if (!alreadyCorrect) {
|
|
3671
3843
|
for (const rec of existingDkim) {
|
|
3672
3844
|
await this.cf.deleteDnsRecord(zoneId, rec.id);
|
|
@@ -4507,7 +4679,7 @@ var GatewayManager = class {
|
|
|
4507
4679
|
html: mail.html || void 0,
|
|
4508
4680
|
replyTo: mail.from,
|
|
4509
4681
|
inReplyTo: mail.inReplyTo,
|
|
4510
|
-
references: mail.references
|
|
4682
|
+
references: Array.isArray(mail.references) ? mail.references.join(" ") : mail.references,
|
|
4511
4683
|
headers: {
|
|
4512
4684
|
"X-AgenticMail-Relay": "inbound",
|
|
4513
4685
|
"X-Original-From": mail.from,
|
|
@@ -4996,7 +5168,7 @@ var GatewayManager = class {
|
|
|
4996
5168
|
html: mail.html || void 0,
|
|
4997
5169
|
replyTo: mail.replyTo || from,
|
|
4998
5170
|
inReplyTo: mail.inReplyTo || void 0,
|
|
4999
|
-
references: mail.references
|
|
5171
|
+
references: Array.isArray(mail.references) ? mail.references.join(" ") : mail.references || void 0,
|
|
5000
5172
|
headers: {
|
|
5001
5173
|
"X-Mailer": "AgenticMail/1.0"
|
|
5002
5174
|
},
|
|
@@ -6777,6 +6949,7 @@ export {
|
|
|
6777
6949
|
TunnelManager,
|
|
6778
6950
|
WARNING_THRESHOLD,
|
|
6779
6951
|
buildInboundSecurityAdvisory,
|
|
6952
|
+
classifyEmailRoute,
|
|
6780
6953
|
closeDatabase,
|
|
6781
6954
|
createTestDatabase,
|
|
6782
6955
|
debug,
|