@bobfrankston/mailx-store 0.1.93 → 0.1.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/package.json +5 -5
  2. package/store.d.ts +4 -1
  3. package/store.js +11 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-store",
3
- "version": "0.1.93",
3
+ "version": "0.1.95",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -9,8 +9,8 @@
9
9
  },
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
- "@bobfrankston/mailx-types": "^0.1.61",
13
- "@bobfrankston/mailx-settings": "^0.1.60",
12
+ "@bobfrankston/mailx-types": "^0.1.63",
13
+ "@bobfrankston/mailx-settings": "^0.1.62",
14
14
  "@bobfrankston/mailx-bus": "^0.1.2",
15
15
  "mailparser": "^3.7.2"
16
16
  },
@@ -29,8 +29,8 @@
29
29
  },
30
30
  ".transformedSnapshot": {
31
31
  "dependencies": {
32
- "@bobfrankston/mailx-types": "^0.1.61",
33
- "@bobfrankston/mailx-settings": "^0.1.60",
32
+ "@bobfrankston/mailx-types": "^0.1.63",
33
+ "@bobfrankston/mailx-settings": "^0.1.62",
34
34
  "@bobfrankston/mailx-bus": "^0.1.2",
35
35
  "mailparser": "^3.7.2"
36
36
  }
package/store.d.ts CHANGED
@@ -19,7 +19,7 @@
19
19
  * be able to consume it without depending on mailx-service — that's the
20
20
  * "arrow points the right way" property of the architecture.
21
21
  */
22
- import { type TrustFinding } from "@bobfrankston/mailx-types";
22
+ import { type TrustFinding, type SpamScore } from "@bobfrankston/mailx-types";
23
23
  import type { ParsedInvite } from "@bobfrankston/mailx-types";
24
24
  import { MailxDB } from "./db.js";
25
25
  import { FileMessageStore } from "./file-store.js";
@@ -57,6 +57,9 @@ export interface StoreMessage extends MessageEnvelope {
57
57
  /** Evidence that this message is forged — empty for ordinary mail. See
58
58
  * assessMessageTrust; the viewer renders these above the body. */
59
59
  trust: TrustFinding[];
60
+ /** The receiving server SpamAssassin score, present or not, flagged or
61
+ * not. Shown as a badge so a below-threshold score is still visible. */
62
+ spamScore: SpamScore;
60
63
  listUnsubscribe: string;
61
64
  listUnsubscribeMail: string;
62
65
  listUnsubscribeHttp: string;
package/store.js CHANGED
@@ -21,7 +21,7 @@
21
21
  */
22
22
  import * as fs from "node:fs";
23
23
  import { parseSerial } from "./parse-serial.js";
24
- import { assessMessageTrust } from "@bobfrankston/mailx-types";
24
+ import { assessMessageTrust, spamScoreOf } from "@bobfrankston/mailx-types";
25
25
  import { parseInvite } from "@bobfrankston/mailx-types";
26
26
  import { storeBus } from "./bus.js";
27
27
  import { sanitizeHtml, htmlToPlainText } from "@bobfrankston/mailx-types";
@@ -543,12 +543,20 @@ export class Store {
543
543
  // needs no configuration and it is exactly the address a self-spoof
544
544
  // forges, so an alias the user never got round to listing is still
545
545
  // covered. (Claude Code 2026-08-25)
546
+ const trustHeaderLines = (parsed.headerLines || []).map(h => ({ key: h.key, line: h.line }));
546
547
  const trust = assessMessageTrust({
547
- headerLines: (parsed.headerLines || []).map(h => ({ key: h.key, line: h.line })),
548
+ headerLines: trustHeaderLines,
548
549
  fromAddress: envelope.from?.address || "",
549
550
  ownAddresses: [...recipients, (deliveredTo.match(/[^\s<>]+@[^\s<>]+/) || [""])[0]].filter(Boolean),
550
551
  bodyHtml,
552
+ bodyText,
551
553
  });
554
+ // The server's score travels separately from the findings: a finding is
555
+ // an accusation and fires only on evidence, this is a measurement the
556
+ // reader asked to see either way (Bob 2026-08-25). A 2.7 against a 4.5
557
+ // threshold is "did not quite trip the wire", which is not the same
558
+ // thing as clean — and that is exactly the message that got through.
559
+ const spamScore = spamScoreOf(trustHeaderLines);
552
560
  if (bodyHtml && !allowRemote) {
553
561
  const result = sanitizeHtml(bodyHtml);
554
562
  bodyHtml = result.html;
@@ -560,7 +568,7 @@ export class Store {
560
568
  hasRemoteContent, remoteAllowed: allowRemote,
561
569
  attachments,
562
570
  cached: true,
563
- deliveredTo, returnPath, trust,
571
+ deliveredTo, returnPath, trust, spamScore,
564
572
  listUnsubscribe, listUnsubscribeMail, listUnsubscribeHttp, listUnsubscribeOneClick,
565
573
  // body_path in the DB is stored relative to the body-store
566
574
  // basePath; resolve to absolute so the UI's "Source" button