@agenticmail/api 0.9.18 → 0.9.20

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/dist/index.js CHANGED
@@ -2468,9 +2468,10 @@ function createMailRoutes(accountManager, config, db, gatewayManager) {
2468
2468
  res.status(400).json({ error: "Invalid UID" });
2469
2469
  return;
2470
2470
  }
2471
+ const folder = req.body?.folder || "INBOX";
2471
2472
  const password = getAgentPassword(agent);
2472
2473
  const receiver = await getReceiver(agent.stalwartPrincipal, password, config);
2473
- await receiver.markUnseen(uid);
2474
+ await receiver.markUnseen(uid, folder);
2474
2475
  invalidateParsedMessage(agent.id, uid);
2475
2476
  res.json({ ok: true });
2476
2477
  } catch (err) {
@@ -2673,6 +2674,17 @@ function createMailRoutes(accountManager, config, db, gatewayManager) {
2673
2674
  next(err);
2674
2675
  }
2675
2676
  });
2677
+ async function resolveSpamFolder(receiver) {
2678
+ const folders = await receiver.listFolders();
2679
+ const junkRe = /^junk\b|^junk mail\b|^spam\b/i;
2680
+ const found = folders.find((f) => f.specialUse === "\\Junk")?.path ?? folders.find((f) => junkRe.test(f.name) || junkRe.test(f.path))?.path;
2681
+ if (found) return found;
2682
+ try {
2683
+ await receiver.createFolder("Junk Mail");
2684
+ } catch {
2685
+ }
2686
+ return "Junk Mail";
2687
+ }
2676
2688
  router.get("/mail/spam", requireAgent, async (req, res, next) => {
2677
2689
  try {
2678
2690
  const agent = req.agent;
@@ -2680,15 +2692,16 @@ function createMailRoutes(accountManager, config, db, gatewayManager) {
2680
2692
  const offset = Math.max(parseInt(req.query.offset) || 0, 0);
2681
2693
  const password = getAgentPassword(agent);
2682
2694
  const receiver = await getReceiver(agent.stalwartPrincipal, password, config);
2695
+ const spamFolder = await resolveSpamFolder(receiver);
2683
2696
  let mailboxInfo;
2684
2697
  try {
2685
- mailboxInfo = await receiver.getMailboxInfo("Spam");
2698
+ mailboxInfo = await receiver.getMailboxInfo(spamFolder);
2686
2699
  } catch {
2687
- res.json({ messages: [], count: 0, total: 0, folder: "Spam" });
2700
+ res.json({ messages: [], count: 0, total: 0, folder: spamFolder });
2688
2701
  return;
2689
2702
  }
2690
- const envelopes = await receiver.listEnvelopes("Spam", { limit, offset });
2691
- res.json({ messages: envelopes, count: envelopes.length, total: mailboxInfo.exists, folder: "Spam" });
2703
+ const envelopes = await receiver.listEnvelopes(spamFolder, { limit, offset });
2704
+ res.json({ messages: envelopes, count: envelopes.length, total: mailboxInfo.exists, folder: spamFolder });
2692
2705
  } catch (err) {
2693
2706
  next(err);
2694
2707
  }
@@ -2793,12 +2806,13 @@ function createMailRoutes(accountManager, config, db, gatewayManager) {
2793
2806
  const folder = req.body?.folder || "INBOX";
2794
2807
  const password = getAgentPassword(agent);
2795
2808
  const receiver = await getReceiver(agent.stalwartPrincipal, password, config);
2796
- try {
2797
- await receiver.createFolder("Spam");
2798
- } catch {
2809
+ const spamFolder = await resolveSpamFolder(receiver);
2810
+ if (spamFolder === folder) {
2811
+ res.status(400).json({ error: "Message already in spam" });
2812
+ return;
2799
2813
  }
2800
- await receiver.moveMessage(uid, folder, "Spam");
2801
- res.json({ ok: true, movedToSpam: true });
2814
+ await receiver.moveMessage(uid, folder, spamFolder);
2815
+ res.json({ ok: true, movedToSpam: true, spam: spamFolder });
2802
2816
  } catch (err) {
2803
2817
  next(err);
2804
2818
  }
@@ -2813,8 +2827,9 @@ function createMailRoutes(accountManager, config, db, gatewayManager) {
2813
2827
  }
2814
2828
  const password = getAgentPassword(agent);
2815
2829
  const receiver = await getReceiver(agent.stalwartPrincipal, password, config);
2816
- await receiver.moveMessage(uid, "Spam", "INBOX");
2817
- res.json({ ok: true, movedToInbox: true });
2830
+ const spamFolder = await resolveSpamFolder(receiver);
2831
+ await receiver.moveMessage(uid, spamFolder, "INBOX");
2832
+ res.json({ ok: true, movedToInbox: true, spam: spamFolder });
2818
2833
  } catch (err) {
2819
2834
  next(err);
2820
2835
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/api",
3
- "version": "0.9.18",
3
+ "version": "0.9.20",
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",
@@ -36,7 +36,20 @@ export async function openMessage(uid) {
36
36
  document.getElementById('msg-delete').addEventListener('click', () => deleteMessage());
37
37
 
38
38
  try {
39
- const msg = await apiGet(`/mail/messages/${uid}`, { agentKey: state.selectedAgent.apiKey });
39
+ // Pass the current folder so the API fetches from the right
40
+ // mailbox — Spam / Archive / Trash UIDs don't exist in INBOX,
41
+ // and the API defaults `folder` to INBOX when omitted. Without
42
+ // this, opening a message from any non-Inbox folder 404'd
43
+ // with `MESSAGE_NOT_FOUND` because UID N existed in (say) Junk
44
+ // Mail but the API looked in INBOX.
45
+ //
46
+ // We resolve the IMAP folder name via state.folderNames (the
47
+ // map populated by /mail/folders auto-discovery) so renames
48
+ // like Stalwart's "Junk Mail" vs "Spam" are handled in one
49
+ // place. "inbox" maps to "INBOX" by convention.
50
+ const imap = state.folderNames?.[state.selectedFolder] ?? 'INBOX';
51
+ const qs = imap && imap !== 'INBOX' ? `?folder=${encodeURIComponent(imap)}` : '';
52
+ const msg = await apiGet(`/mail/messages/${uid}${qs}`, { agentKey: state.selectedAgent.apiKey });
40
53
  state.currentMessage = msg;
41
54
  renderMessage(msg);
42
55
  } catch (err) {
@@ -220,7 +233,8 @@ function renderThreadQuote(dateRaw, sender, quotedBody) {
220
233
  async function markUnread() {
221
234
  if (!state.currentMessage || !state.selectedAgent) return;
222
235
  try {
223
- await apiPost(`/mail/messages/${state.selectedUid}/unseen`, {}, { agentKey: state.selectedAgent.apiKey });
236
+ const imap = state.folderNames?.[state.selectedFolder] ?? 'INBOX';
237
+ await apiPost(`/mail/messages/${state.selectedUid}/unseen`, { folder: imap }, { agentKey: state.selectedAgent.apiKey });
224
238
  toast('Marked unread.');
225
239
  location.hash = `#/folder/${state.selectedFolder ?? 'inbox'}`;
226
240
  await loadList(state.selectedAgent, state.selectedFolder);
@@ -262,7 +276,8 @@ async function markSpam() {
262
276
  });
263
277
  if (!ok) return;
264
278
  try {
265
- await apiPost(`/mail/messages/${state.selectedUid}/spam`, {}, { agentKey: state.selectedAgent.apiKey });
279
+ const imap = state.folderNames?.[state.selectedFolder] ?? 'INBOX';
280
+ await apiPost(`/mail/messages/${state.selectedUid}/spam`, { folder: imap }, { agentKey: state.selectedAgent.apiKey });
266
281
  toast('Reported as spam.');
267
282
  location.hash = `#/folder/${state.selectedFolder ?? 'inbox'}`;
268
283
  await loadList(state.selectedAgent, state.selectedFolder);