@agenticmail/core 0.7.1 → 0.7.3
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 +18 -5
- package/REFERENCE.md +1 -1
- package/dist/index.cjs +22 -4
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +22 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/agenticmail/agenticmail/main/docs/images/logo-200.png" alt="AgenticMail logo (pink bow)" width="180" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">@agenticmail/core</h1>
|
|
2
6
|
|
|
3
7
|
Core SDK for [AgenticMail](https://github.com/agenticmail/agenticmail) — the first platform to give AI agents real email addresses and phone numbers.
|
|
4
8
|
|
|
@@ -6,6 +10,13 @@ This is the foundation layer that everything else builds on. If the API server,
|
|
|
6
10
|
|
|
7
11
|
Every other AgenticMail package depends on this one.
|
|
8
12
|
|
|
13
|
+
## ✨ What's new in 0.7.2
|
|
14
|
+
|
|
15
|
+
- **`list_inbox` / `inbox_digest` consistency fix** — `MailReceiver.listEnvelopes` no longer trusts the stale cached `mailbox.exists` count and early-returns empty when an internal mail just landed. `getMailboxInfo` now issues an IMAP `NOOP` to refresh state before reading `client.mailbox`. `SEARCH` is the authoritative source; `list_inbox` and `inbox_digest` now agree.
|
|
16
|
+
- **Custom outgoing headers** — the `headers` field on `SendMailOptions` is fully plumbed through to nodemailer, so callers (the API, the MCP server) can set `X-AgenticMail-Wake` and other custom headers for downstream consumers to read.
|
|
17
|
+
|
|
18
|
+
The wake / thread-close / web UI features live in the API and dispatcher layers above this package; `core` provides the primitives they all share.
|
|
19
|
+
|
|
9
20
|
---
|
|
10
21
|
|
|
11
22
|
## Table of Contents
|
|
@@ -174,7 +185,7 @@ Full custom domain through Cloudflare. Agents send from `agent@yourdomain.com` w
|
|
|
174
185
|
4. Stalwart hostname set to the domain (critical for SMTP EHLO greeting)
|
|
175
186
|
5. DKIM signing key generated in Stalwart (selector: `agenticmail`)
|
|
176
187
|
6. DNS records configured: MX (via Email Routing), SPF, DMARC, DKIM TXT, tunnel CNAME
|
|
177
|
-
7. Tunnel started with ingress rules routing traffic to the API server (port
|
|
188
|
+
7. Tunnel started with ingress rules routing traffic to the API server (port 3829) and Stalwart (port 8080)
|
|
178
189
|
8. Cloudflare Email Routing enabled on the zone
|
|
179
190
|
9. Email Worker deployed — catches all inbound email, base64-encodes the raw RFC822 content, and POSTs it to the AgenticMail inbound webhook with a shared secret
|
|
180
191
|
10. Catch-all Email Routing rule set to route all `*@domain` to the Worker
|
|
@@ -410,7 +421,9 @@ Limits: minimum 1 result, maximum 1000 results per query (default 20).
|
|
|
410
421
|
|
|
411
422
|
## Data Storage
|
|
412
423
|
|
|
413
|
-
AgenticMail uses
|
|
424
|
+
AgenticMail uses Node's built-in **`node:sqlite`** module (stable since Node 22). The database runs with WAL mode (Write-Ahead Logging) for better concurrent access and foreign keys enabled for referential integrity. Automatic migrations run on first `getDatabase()` call.
|
|
425
|
+
|
|
426
|
+
> **Why `node:sqlite` instead of `better-sqlite3`?** Before `0.7.0` this package depended on `better-sqlite3`, a native module that ships pre-built binaries per `NODE_MODULE_VERSION` and intermittently lags new Node releases. When prebuilds were missing, installers fell back to `node-gyp` compile-from-source — which requires Python, a C++ toolchain, and a working network at install time. `node:sqlite` is part of Node itself, so by definition it always matches the runtime. No prebuilds, no gyp, no native compilation, no Python prereq. The on-disk database format is unchanged (still SQLite 3); existing data files migrate transparently. **Cost:** Node 22+ is now the minimum supported runtime.
|
|
414
427
|
|
|
415
428
|
**Database location:** `~/.agenticmail/agenticmail.db`
|
|
416
429
|
|
|
@@ -447,14 +460,14 @@ The SetupManager handles getting everything installed and configured for the fir
|
|
|
447
460
|
- cloudflared — checks managed binary at `~/.agenticmail/bin/cloudflared` or system-wide via `which`
|
|
448
461
|
|
|
449
462
|
**Automatic installation:**
|
|
450
|
-
- Docker: via Homebrew (`brew install
|
|
463
|
+
- Docker: via Homebrew (`brew install colima docker docker-compose`) on macOS — **uses Colima, not Docker Desktop**, so there's no GUI gate and no terms-acceptance dialog. On Linux, the official install script. Starts Colima (`colima start --cpu 2 --memory 2 --disk 10`) and waits for the daemon to come up.
|
|
451
464
|
- Stalwart: starts the container via `docker compose up -d` and waits up to 30 seconds for it to be running.
|
|
452
465
|
- cloudflared: downloads the platform-specific binary from GitHub releases (supports macOS ARM/Intel and Linux ARM/Intel). Installs atomically (write to temp file, chmod, rename) at `~/.agenticmail/bin/cloudflared`.
|
|
453
466
|
|
|
454
467
|
**Configuration generation:**
|
|
455
468
|
- `docker-compose.yml` — Stalwart service with ports 8080 (HTTP admin), 587 (SMTP submission), 143 (IMAP), 25 (SMTP inbound)
|
|
456
469
|
- `stalwart.toml` — Stalwart configuration with RocksDB storage, internal directory, stdout logging, and fallback admin credentials
|
|
457
|
-
- `config.json` — Master key, Stalwart URL/credentials, SMTP/IMAP host/port, API host/
|
|
470
|
+
- `config.json` — Master key, Stalwart URL/credentials, SMTP/IMAP host/port, API host (default `127.0.0.1`) and API port (**default `3829`** — chosen to avoid common dev-tool ports like 3000/3100/3200/3300/4000/5000/8000/8080), data directory (written with mode 0600 for security)
|
|
458
471
|
- `.env` — Environment variables (written with mode 0600)
|
|
459
472
|
|
|
460
473
|
Configuration files are placed in the data directory (default: `~/.agenticmail/`). Calling `initConfig()` is idempotent — it loads existing config if present, but always regenerates Docker files to keep passwords in sync.
|
package/REFERENCE.md
CHANGED
|
@@ -825,7 +825,7 @@ class CloudflareClient {
|
|
|
825
825
|
|
|
826
826
|
**`createTunnel()`** — Reuses existing tunnel if name matches. Generates random 32-byte secret.
|
|
827
827
|
|
|
828
|
-
**`createTunnelRoute()`** — Creates ingress: `/api/agenticmail/*` → apiService (port
|
|
828
|
+
**`createTunnelRoute()`** — Creates ingress: `/api/agenticmail/*` → apiService (port 3829), `*` → primary service (port 8080), catch-all → 404.
|
|
829
829
|
|
|
830
830
|
**`deployEmailWorker()`** — Multipart form upload with ES module metadata and plain_text env var bindings. Compatibility date: 2024-01-01.
|
|
831
831
|
|
package/dist/index.cjs
CHANGED
|
@@ -894,6 +894,10 @@ var MailReceiver = class {
|
|
|
894
894
|
async getMailboxInfo(mailbox = "INBOX") {
|
|
895
895
|
const lock = await this.client.getMailboxLock(mailbox);
|
|
896
896
|
try {
|
|
897
|
+
try {
|
|
898
|
+
await this.client.noop();
|
|
899
|
+
} catch {
|
|
900
|
+
}
|
|
897
901
|
const status = this.client.mailbox;
|
|
898
902
|
if (!status) {
|
|
899
903
|
return { name: mailbox, exists: 0, recent: 0, unseen: 0 };
|
|
@@ -912,12 +916,8 @@ var MailReceiver = class {
|
|
|
912
916
|
const lock = await this.client.getMailboxLock(mailbox);
|
|
913
917
|
try {
|
|
914
918
|
const envelopes = [];
|
|
915
|
-
const mb = this.client.mailbox;
|
|
916
|
-
const total = mb ? mb.exists ?? 0 : 0;
|
|
917
|
-
if (total === 0) return envelopes;
|
|
918
919
|
const limit = Math.min(Math.max(options?.limit ?? 20, 1), 1e3);
|
|
919
920
|
const offset = Math.max(options?.offset ?? 0, 0);
|
|
920
|
-
if (offset >= total) return envelopes;
|
|
921
921
|
const allUids = await this.client.search({ all: true }, { uid: true });
|
|
922
922
|
if (!allUids || allUids.length === 0) return envelopes;
|
|
923
923
|
const sortedUids = Array.from(allUids).sort((a, b) => b - a);
|
|
@@ -1011,6 +1011,24 @@ var MailReceiver = class {
|
|
|
1011
1011
|
lock.release();
|
|
1012
1012
|
}
|
|
1013
1013
|
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Flag / unflag a message. IMAP uses `\Flagged` for what Gmail
|
|
1016
|
+
* calls "starred" — same on-disk bit, different vocabulary. We
|
|
1017
|
+
* expose it as `setStarred(uid, true|false)` so the web UI can
|
|
1018
|
+
* call a single endpoint with a boolean.
|
|
1019
|
+
*/
|
|
1020
|
+
async setStarred(uid, starred, mailbox = "INBOX") {
|
|
1021
|
+
const lock = await this.client.getMailboxLock(mailbox);
|
|
1022
|
+
try {
|
|
1023
|
+
if (starred) {
|
|
1024
|
+
await this.client.messageFlagsAdd(String(uid), ["\\Flagged"], { uid: true });
|
|
1025
|
+
} else {
|
|
1026
|
+
await this.client.messageFlagsRemove(String(uid), ["\\Flagged"], { uid: true });
|
|
1027
|
+
}
|
|
1028
|
+
} finally {
|
|
1029
|
+
lock.release();
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1014
1032
|
/** Move a message to another folder */
|
|
1015
1033
|
async moveMessage(uid, fromMailbox, toMailbox) {
|
|
1016
1034
|
const lock = await this.client.getMailboxLock(fromMailbox);
|
package/dist/index.d.cts
CHANGED
|
@@ -473,6 +473,13 @@ declare class MailReceiver {
|
|
|
473
473
|
deleteMessage(uid: number, mailbox?: string): Promise<void>;
|
|
474
474
|
/** Mark a message as unseen (unread) */
|
|
475
475
|
markUnseen(uid: number, mailbox?: string): Promise<void>;
|
|
476
|
+
/**
|
|
477
|
+
* Flag / unflag a message. IMAP uses `\Flagged` for what Gmail
|
|
478
|
+
* calls "starred" — same on-disk bit, different vocabulary. We
|
|
479
|
+
* expose it as `setStarred(uid, true|false)` so the web UI can
|
|
480
|
+
* call a single endpoint with a boolean.
|
|
481
|
+
*/
|
|
482
|
+
setStarred(uid: number, starred: boolean, mailbox?: string): Promise<void>;
|
|
476
483
|
/** Move a message to another folder */
|
|
477
484
|
moveMessage(uid: number, fromMailbox: string, toMailbox: string): Promise<void>;
|
|
478
485
|
/** List all IMAP folders/mailboxes */
|
package/dist/index.d.ts
CHANGED
|
@@ -473,6 +473,13 @@ declare class MailReceiver {
|
|
|
473
473
|
deleteMessage(uid: number, mailbox?: string): Promise<void>;
|
|
474
474
|
/** Mark a message as unseen (unread) */
|
|
475
475
|
markUnseen(uid: number, mailbox?: string): Promise<void>;
|
|
476
|
+
/**
|
|
477
|
+
* Flag / unflag a message. IMAP uses `\Flagged` for what Gmail
|
|
478
|
+
* calls "starred" — same on-disk bit, different vocabulary. We
|
|
479
|
+
* expose it as `setStarred(uid, true|false)` so the web UI can
|
|
480
|
+
* call a single endpoint with a boolean.
|
|
481
|
+
*/
|
|
482
|
+
setStarred(uid: number, starred: boolean, mailbox?: string): Promise<void>;
|
|
476
483
|
/** Move a message to another folder */
|
|
477
484
|
moveMessage(uid: number, fromMailbox: string, toMailbox: string): Promise<void>;
|
|
478
485
|
/** List all IMAP folders/mailboxes */
|
package/dist/index.js
CHANGED
|
@@ -140,6 +140,10 @@ var MailReceiver = class {
|
|
|
140
140
|
async getMailboxInfo(mailbox = "INBOX") {
|
|
141
141
|
const lock = await this.client.getMailboxLock(mailbox);
|
|
142
142
|
try {
|
|
143
|
+
try {
|
|
144
|
+
await this.client.noop();
|
|
145
|
+
} catch {
|
|
146
|
+
}
|
|
143
147
|
const status = this.client.mailbox;
|
|
144
148
|
if (!status) {
|
|
145
149
|
return { name: mailbox, exists: 0, recent: 0, unseen: 0 };
|
|
@@ -158,12 +162,8 @@ var MailReceiver = class {
|
|
|
158
162
|
const lock = await this.client.getMailboxLock(mailbox);
|
|
159
163
|
try {
|
|
160
164
|
const envelopes = [];
|
|
161
|
-
const mb = this.client.mailbox;
|
|
162
|
-
const total = mb ? mb.exists ?? 0 : 0;
|
|
163
|
-
if (total === 0) return envelopes;
|
|
164
165
|
const limit = Math.min(Math.max(options?.limit ?? 20, 1), 1e3);
|
|
165
166
|
const offset = Math.max(options?.offset ?? 0, 0);
|
|
166
|
-
if (offset >= total) return envelopes;
|
|
167
167
|
const allUids = await this.client.search({ all: true }, { uid: true });
|
|
168
168
|
if (!allUids || allUids.length === 0) return envelopes;
|
|
169
169
|
const sortedUids = Array.from(allUids).sort((a, b) => b - a);
|
|
@@ -257,6 +257,24 @@ var MailReceiver = class {
|
|
|
257
257
|
lock.release();
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* Flag / unflag a message. IMAP uses `\Flagged` for what Gmail
|
|
262
|
+
* calls "starred" — same on-disk bit, different vocabulary. We
|
|
263
|
+
* expose it as `setStarred(uid, true|false)` so the web UI can
|
|
264
|
+
* call a single endpoint with a boolean.
|
|
265
|
+
*/
|
|
266
|
+
async setStarred(uid, starred, mailbox = "INBOX") {
|
|
267
|
+
const lock = await this.client.getMailboxLock(mailbox);
|
|
268
|
+
try {
|
|
269
|
+
if (starred) {
|
|
270
|
+
await this.client.messageFlagsAdd(String(uid), ["\\Flagged"], { uid: true });
|
|
271
|
+
} else {
|
|
272
|
+
await this.client.messageFlagsRemove(String(uid), ["\\Flagged"], { uid: true });
|
|
273
|
+
}
|
|
274
|
+
} finally {
|
|
275
|
+
lock.release();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
260
278
|
/** Move a message to another folder */
|
|
261
279
|
async moveMessage(uid, fromMailbox, toMailbox) {
|
|
262
280
|
const lock = await this.client.getMailboxLock(fromMailbox);
|