@bobfrankston/iflow-direct 0.1.55 → 0.1.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.
@@ -19,6 +19,7 @@ export declare class FetchedMessage implements NativeFetchedMessage {
19
19
  uid: number;
20
20
  flags: Set<string>;
21
21
  date: Date | null;
22
+ sentDate?: Date;
22
23
  subject: string;
23
24
  messageId: string;
24
25
  from: AddressData[];
@@ -24,6 +24,7 @@ export class FetchedMessage {
24
24
  uid;
25
25
  flags;
26
26
  date;
27
+ sentDate;
27
28
  subject;
28
29
  messageId;
29
30
  from;
package/imap-native.d.ts CHANGED
@@ -14,6 +14,7 @@ export interface NativeFetchedMessage {
14
14
  uid: number;
15
15
  flags: Set<string>;
16
16
  date: Date | null;
17
+ sentDate?: Date;
17
18
  subject: string;
18
19
  messageId: string;
19
20
  from: proto.AddressData[];
package/imap-native.js CHANGED
@@ -706,6 +706,17 @@ export class NativeImapClient {
706
706
  return proto.parseSearchResponse(r.text);
707
707
  }
708
708
  }
709
+ // No untagged * SEARCH seen. If the tagged status is anything but OK,
710
+ // the search FAILED — throw so callers can tell "failed" from "no
711
+ // matches". Silently returning [] here let a failed HEADER search read
712
+ // as "message missing from server", and mailx's sent-sweep then
713
+ // re-APPENDed a copy every 5-minute tick — 3,704 duplicates of one
714
+ // message over ~13 days (2026-07-02). Empty-but-OK still returns []
715
+ // (some servers omit the * SEARCH line when there are zero hits).
716
+ const tagged = responses.find(r => r.tag === tag);
717
+ if (!tagged || tagged.type !== "OK") {
718
+ throw new Error(`SEARCH failed: ${tagged ? `${tagged.type} ${tagged.text || ""}` : "no tagged response"}`);
719
+ }
709
720
  return [];
710
721
  }
711
722
  /** Set flags on a message */
@@ -1513,6 +1524,11 @@ export class NativeImapClient {
1513
1524
  if (envMatch) {
1514
1525
  const env = proto.parseEnvelope(envMatch[1]);
1515
1526
  msg.date = msg.date || env.date;
1527
+ // Expose the ENVELOPE Date: header (the SENT time) separately
1528
+ // from msg.date (INTERNALDATE = server-received time). Callers
1529
+ // that want messages to cluster by when they were sent — not by
1530
+ // per-account delivery time — use sentDate. Additive/optional.
1531
+ msg.sentDate = env.date || undefined;
1516
1532
  msg.subject = env.subject;
1517
1533
  msg.messageId = env.messageId;
1518
1534
  msg.from = env.from;
package/imap-protocol.js CHANGED
@@ -302,8 +302,16 @@ export function parseEnvelope(envStr) {
302
302
  result.to = parseAddressList(tokens[5]);
303
303
  result.cc = parseAddressList(tokens[6]);
304
304
  result.bcc = parseAddressList(tokens[7]);
305
- result.inReplyTo = unquote(tokens[8]);
306
- result.messageId = unquote(tokens[9]);
305
+ // Trim fold whitespace. Servers hand back the ENVELOPE msg-id /
306
+ // in-reply-to with the original header's folding intact when the
307
+ // Message-ID sat on a continuation line ("Message-ID:\r\n\t<...>")
308
+ // — the value arrives as "\t<...>". Stored untrimmed, that tab
309
+ // poisoned every later HEADER search ("missing on server") and
310
+ // fueled mailx's sent-sweep re-APPEND loop: 3,707 duplicates of
311
+ // one Outlook-composed reply (2026-07-02). Trim here so no
312
+ // consumer ever sees the fold whitespace.
313
+ result.inReplyTo = unquote(tokens[8]).trim();
314
+ result.messageId = unquote(tokens[9]).trim();
307
315
  }
308
316
  }
309
317
  catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/iflow-direct",
3
- "version": "0.1.55",
3
+ "version": "0.1.56",
4
4
  "description": "Direct IMAP client — transport-agnostic, no Node.js dependencies, browser-ready",
5
5
  "main": "index.js",
6
6
  "types": "index.ts",