@agenticmail/api 0.5.60 → 0.5.62

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 (2) hide show
  1. package/dist/index.js +84 -7
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1539,7 +1539,65 @@ async function closeCaches() {
1539
1539
  }
1540
1540
  receiverCache.clear();
1541
1541
  }
1542
- async function notifyLocalRecipientsOfNewMail(accountManager, toField, ccField, bccField, fromAgent, subject, messageId) {
1542
+ async function findUidByMessageId(receiver, messageId, maxAttempts = 8) {
1543
+ const target = normalizeMessageId(messageId);
1544
+ const client = receiver.getImapClient();
1545
+ const tryHeaderSearch = async () => {
1546
+ const lock = await client.getMailboxLock("INBOX");
1547
+ try {
1548
+ const results = await client.search(
1549
+ { header: ["Message-ID", messageId] },
1550
+ { uid: true }
1551
+ );
1552
+ if (Array.isArray(results) && results.length > 0) {
1553
+ return results[results.length - 1];
1554
+ }
1555
+ } finally {
1556
+ lock.release();
1557
+ }
1558
+ return 0;
1559
+ };
1560
+ const tryEnvelopeScan = async () => {
1561
+ const lock = await client.getMailboxLock("INBOX");
1562
+ try {
1563
+ const status = await client.status("INBOX", { messages: true });
1564
+ const total = status?.messages ?? 0;
1565
+ if (total === 0) return 0;
1566
+ const start = Math.max(1, total - 9);
1567
+ const range = `${start}:${total}`;
1568
+ let bestUid = 0;
1569
+ for await (const msg of client.fetch(range, { uid: true, envelope: true })) {
1570
+ if (!msg.envelope?.messageId) continue;
1571
+ if (normalizeMessageId(msg.envelope.messageId) === target) {
1572
+ if (msg.uid > bestUid) bestUid = msg.uid;
1573
+ }
1574
+ }
1575
+ return bestUid;
1576
+ } finally {
1577
+ lock.release();
1578
+ }
1579
+ };
1580
+ const delays = [0, 250, 500, 750, 1e3, 1250, 1500, 2e3];
1581
+ for (let i = 0; i < maxAttempts; i++) {
1582
+ if (delays[i]) await new Promise((r) => setTimeout(r, delays[i]));
1583
+ try {
1584
+ const headerHit = await tryHeaderSearch();
1585
+ if (headerHit > 0) return headerHit;
1586
+ const scanHit = await tryEnvelopeScan();
1587
+ if (scanHit > 0) return scanHit;
1588
+ } catch (err) {
1589
+ if (i === maxAttempts - 1) {
1590
+ console.warn(`[mail] findUidByMessageId attempt ${i + 1} failed for ${messageId}: ${err.message}`);
1591
+ }
1592
+ }
1593
+ }
1594
+ return 0;
1595
+ }
1596
+ function normalizeMessageId(id) {
1597
+ if (!id) return "";
1598
+ return id.trim().replace(/^<+|>+$/g, "").toLowerCase();
1599
+ }
1600
+ async function notifyLocalRecipientsOfNewMail(accountManager, toField, ccField, bccField, fromAgent, subject, messageId, config) {
1543
1601
  const collected = [];
1544
1602
  const push = (v) => {
1545
1603
  if (!v) return;
@@ -1574,12 +1632,29 @@ async function notifyLocalRecipientsOfNewMail(accountManager, toField, ccField,
1574
1632
  }
1575
1633
  if (!recipient || notified.has(recipient.id)) continue;
1576
1634
  notified.add(recipient.id);
1635
+ let uid = 0;
1636
+ let lookup = "no-message-id";
1637
+ if (messageId) {
1638
+ try {
1639
+ const recipientPassword = getAgentPassword(recipient);
1640
+ const receiver = await getReceiver(
1641
+ recipient.stalwartPrincipal,
1642
+ recipientPassword,
1643
+ config
1644
+ );
1645
+ uid = await findUidByMessageId(receiver, messageId);
1646
+ lookup = uid > 0 ? "resolved" : "failed";
1647
+ } catch {
1648
+ lookup = "failed";
1649
+ }
1650
+ }
1577
1651
  pushEventToAgent(recipient.id, {
1578
1652
  type: "new",
1579
- // uid is unknown without an IMAP fetch; use 0 as a sentinel —
1580
- // this matches the watcher's autoFetch=false path. SSE consumers
1581
- // that want full message detail can call /mail/inbox.
1582
- uid: 0,
1653
+ uid,
1654
+ // Tell consumers whether the UID is real or a sentinel — preserves
1655
+ // backwards compat (uid is still always a number) while giving
1656
+ // clients a reliable signal to fall back to /mail/inbox.
1657
+ uidLookup: lookup,
1583
1658
  internal: true,
1584
1659
  from: { name: fromAgent.name, address: fromAgent.email },
1585
1660
  subject,
@@ -1726,7 +1801,8 @@ function createMailRoutes(accountManager, config, db, gatewayManager) {
1726
1801
  bcc,
1727
1802
  agent,
1728
1803
  subject,
1729
- result.messageId
1804
+ result.messageId,
1805
+ config
1730
1806
  ).catch((err) => {
1731
1807
  console.warn(`[mail] Internal SSE notify failed: ${err.message}`);
1732
1808
  });
@@ -2336,7 +2412,8 @@ function createMailRoutes(accountManager, config, db, gatewayManager) {
2336
2412
  mailOpts.bcc,
2337
2413
  agent,
2338
2414
  mailOpts.subject,
2339
- result.messageId
2415
+ result.messageId,
2416
+ config
2340
2417
  ).catch((err) => {
2341
2418
  console.warn(`[mail] Internal SSE notify (approve) failed: ${err.message}`);
2342
2419
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/api",
3
- "version": "0.5.60",
3
+ "version": "0.5.62",
4
4
  "description": "REST API server for AgenticMail — email and SMS endpoints for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -27,7 +27,7 @@
27
27
  "prepublishOnly": "npm run build"
28
28
  },
29
29
  "dependencies": {
30
- "@agenticmail/core": "^0.5.59",
30
+ "@agenticmail/core": "^0.5.61",
31
31
  "cors": "^2.8.5",
32
32
  "dotenv": "^16.4.7",
33
33
  "express": "^4.21.0",