@agenticmail/api 0.5.59 → 0.5.61
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 +91 -48
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -264,17 +264,17 @@ function createAccountRoutes(accountManager, db, config) {
|
|
|
264
264
|
const router = Router2();
|
|
265
265
|
const deletionService = new AgentDeletionService(db, accountManager, config);
|
|
266
266
|
router.post("/accounts", requireMaster, async (req, res, next) => {
|
|
267
|
+
if (!req.body || typeof req.body !== "object") {
|
|
268
|
+
res.status(400).json({ error: "Request body must be JSON" });
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const { name: accountName, domain, password, metadata, role, persistent } = req.body;
|
|
267
272
|
try {
|
|
268
|
-
if (!
|
|
269
|
-
res.status(400).json({ error: "Request body must be JSON" });
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
const { name: name2, domain, password, metadata, role, persistent } = req.body;
|
|
273
|
-
if (!name2 || typeof name2 !== "string") {
|
|
273
|
+
if (!accountName || typeof accountName !== "string") {
|
|
274
274
|
res.status(400).json({ error: "name is required and must be a string" });
|
|
275
275
|
return;
|
|
276
276
|
}
|
|
277
|
-
if (
|
|
277
|
+
if (accountName.length > 64) {
|
|
278
278
|
res.status(400).json({ error: "name must be 64 characters or fewer" });
|
|
279
279
|
return;
|
|
280
280
|
}
|
|
@@ -293,7 +293,7 @@ function createAccountRoutes(accountManager, db, config) {
|
|
|
293
293
|
const cleanMeta = metadata ? Object.fromEntries(
|
|
294
294
|
Object.entries(metadata).filter(([k]) => !k.startsWith("_"))
|
|
295
295
|
) : void 0;
|
|
296
|
-
const agent = await accountManager.create({ name:
|
|
296
|
+
const agent = await accountManager.create({ name: accountName, domain, password: password || void 0, metadata: cleanMeta, role });
|
|
297
297
|
try {
|
|
298
298
|
db.prepare("UPDATE agents SET last_activity_at = datetime('now') WHERE id = ?").run(agent.id);
|
|
299
299
|
} catch {
|
|
@@ -310,7 +310,7 @@ function createAccountRoutes(accountManager, db, config) {
|
|
|
310
310
|
} catch (err) {
|
|
311
311
|
const msg = err.message ?? "";
|
|
312
312
|
if (msg.includes("UNIQUE") || msg.includes("unique") || msg.includes("already exists") || msg.includes("duplicate") || msg.includes("fieldAlreadyExists") || msg.toLowerCase().includes("alreadyexists")) {
|
|
313
|
-
res.status(409).json({ error: "Account already exists", name });
|
|
313
|
+
res.status(409).json({ error: "Account already exists", name: accountName });
|
|
314
314
|
return;
|
|
315
315
|
}
|
|
316
316
|
next(err);
|
|
@@ -644,13 +644,13 @@ function createFeatureRoutes(db, _accountManager, config, gatewayManager) {
|
|
|
644
644
|
});
|
|
645
645
|
router.post("/contacts", requireAgent, async (req, res, next) => {
|
|
646
646
|
try {
|
|
647
|
-
const { name
|
|
647
|
+
const { name, email, notes } = req.body || {};
|
|
648
648
|
if (!email) {
|
|
649
649
|
res.status(400).json({ error: "email is required" });
|
|
650
650
|
return;
|
|
651
651
|
}
|
|
652
652
|
const id = uuidv4();
|
|
653
|
-
db.prepare("INSERT OR REPLACE INTO contacts (id, agent_id, name, email, notes) VALUES (?, ?, ?, ?, ?)").run(id, req.agent.id,
|
|
653
|
+
db.prepare("INSERT OR REPLACE INTO contacts (id, agent_id, name, email, notes) VALUES (?, ?, ?, ?, ?)").run(id, req.agent.id, name || null, email, notes || null);
|
|
654
654
|
res.json({ ok: true, id, email });
|
|
655
655
|
} catch (err) {
|
|
656
656
|
next(err);
|
|
@@ -795,8 +795,8 @@ function createFeatureRoutes(db, _accountManager, config, gatewayManager) {
|
|
|
795
795
|
});
|
|
796
796
|
router.post("/signatures", requireAgent, async (req, res, next) => {
|
|
797
797
|
try {
|
|
798
|
-
const { name
|
|
799
|
-
if (!
|
|
798
|
+
const { name, text, html, isDefault } = req.body || {};
|
|
799
|
+
if (!name) {
|
|
800
800
|
res.status(400).json({ error: "name is required" });
|
|
801
801
|
return;
|
|
802
802
|
}
|
|
@@ -804,7 +804,7 @@ function createFeatureRoutes(db, _accountManager, config, gatewayManager) {
|
|
|
804
804
|
if (isDefault) {
|
|
805
805
|
db.prepare("UPDATE signatures SET is_default = 0 WHERE agent_id = ?").run(req.agent.id);
|
|
806
806
|
}
|
|
807
|
-
db.prepare("INSERT OR REPLACE INTO signatures (id, agent_id, name, text_content, html_content, is_default) VALUES (?, ?, ?, ?, ?, ?)").run(id, req.agent.id,
|
|
807
|
+
db.prepare("INSERT OR REPLACE INTO signatures (id, agent_id, name, text_content, html_content, is_default) VALUES (?, ?, ?, ?, ?, ?)").run(id, req.agent.id, name, text || null, html || null, isDefault ? 1 : 0);
|
|
808
808
|
res.json({ ok: true, id });
|
|
809
809
|
} catch (err) {
|
|
810
810
|
next(err);
|
|
@@ -832,13 +832,13 @@ function createFeatureRoutes(db, _accountManager, config, gatewayManager) {
|
|
|
832
832
|
});
|
|
833
833
|
router.post("/templates", requireAgent, async (req, res, next) => {
|
|
834
834
|
try {
|
|
835
|
-
const { name
|
|
836
|
-
if (!
|
|
835
|
+
const { name, subject, text, html } = req.body || {};
|
|
836
|
+
if (!name) {
|
|
837
837
|
res.status(400).json({ error: "name is required" });
|
|
838
838
|
return;
|
|
839
839
|
}
|
|
840
840
|
const id = uuidv4();
|
|
841
|
-
db.prepare("INSERT OR REPLACE INTO templates (id, agent_id, name, subject, text_body, html_body) VALUES (?, ?, ?, ?, ?, ?)").run(id, req.agent.id,
|
|
841
|
+
db.prepare("INSERT OR REPLACE INTO templates (id, agent_id, name, subject, text_body, html_body) VALUES (?, ?, ?, ?, ?, ?)").run(id, req.agent.id, name, subject || null, text || null, html || null);
|
|
842
842
|
res.json({ ok: true, id });
|
|
843
843
|
} catch (err) {
|
|
844
844
|
next(err);
|
|
@@ -912,14 +912,14 @@ function createFeatureRoutes(db, _accountManager, config, gatewayManager) {
|
|
|
912
912
|
});
|
|
913
913
|
router.post("/tags", requireAgent, async (req, res, next) => {
|
|
914
914
|
try {
|
|
915
|
-
const { name
|
|
916
|
-
if (!
|
|
915
|
+
const { name, color } = req.body || {};
|
|
916
|
+
if (!name) {
|
|
917
917
|
res.status(400).json({ error: "name is required" });
|
|
918
918
|
return;
|
|
919
919
|
}
|
|
920
920
|
const id = uuidv4();
|
|
921
|
-
db.prepare("INSERT OR IGNORE INTO tags (id, agent_id, name, color) VALUES (?, ?, ?, ?)").run(id, req.agent.id,
|
|
922
|
-
res.json({ ok: true, id, name:
|
|
921
|
+
db.prepare("INSERT OR IGNORE INTO tags (id, agent_id, name, color) VALUES (?, ?, ?, ?)").run(id, req.agent.id, name.trim(), color || "#888888");
|
|
922
|
+
res.json({ ok: true, id, name: name.trim(), color: color || "#888888" });
|
|
923
923
|
} catch (err) {
|
|
924
924
|
next(err);
|
|
925
925
|
}
|
|
@@ -1049,16 +1049,16 @@ function createFeatureRoutes(db, _accountManager, config, gatewayManager) {
|
|
|
1049
1049
|
});
|
|
1050
1050
|
router.post("/rules", requireAgent, async (req, res, next) => {
|
|
1051
1051
|
try {
|
|
1052
|
-
const { name
|
|
1053
|
-
if (!
|
|
1052
|
+
const { name, conditions, actions, priority, enabled } = req.body || {};
|
|
1053
|
+
if (!name) {
|
|
1054
1054
|
res.status(400).json({ error: "name is required" });
|
|
1055
1055
|
return;
|
|
1056
1056
|
}
|
|
1057
1057
|
const id = uuidv4();
|
|
1058
1058
|
db.prepare(
|
|
1059
1059
|
"INSERT INTO email_rules (id, agent_id, name, priority, enabled, conditions, actions) VALUES (?, ?, ?, ?, ?, ?, ?)"
|
|
1060
|
-
).run(id, req.agent.id,
|
|
1061
|
-
res.status(201).json({ id, name
|
|
1060
|
+
).run(id, req.agent.id, name, priority ?? 0, enabled !== false ? 1 : 0, JSON.stringify(conditions || {}), JSON.stringify(actions || {}));
|
|
1061
|
+
res.status(201).json({ id, name, conditions: conditions || {}, actions: actions || {}, priority: priority ?? 0, enabled: enabled !== false });
|
|
1062
1062
|
} catch (err) {
|
|
1063
1063
|
next(err);
|
|
1064
1064
|
}
|
|
@@ -1539,7 +1539,31 @@ async function closeCaches() {
|
|
|
1539
1539
|
}
|
|
1540
1540
|
receiverCache.clear();
|
|
1541
1541
|
}
|
|
1542
|
-
async function
|
|
1542
|
+
async function findUidByMessageId(receiver, messageId, maxAttempts = 5) {
|
|
1543
|
+
const client = receiver.getImapClient();
|
|
1544
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
1545
|
+
try {
|
|
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
|
+
} catch {
|
|
1559
|
+
}
|
|
1560
|
+
if (i < maxAttempts - 1) {
|
|
1561
|
+
await new Promise((r) => setTimeout(r, 200 * (i + 1)));
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
return 0;
|
|
1565
|
+
}
|
|
1566
|
+
async function notifyLocalRecipientsOfNewMail(accountManager, toField, ccField, bccField, fromAgent, subject, messageId, config) {
|
|
1543
1567
|
const collected = [];
|
|
1544
1568
|
const push = (v) => {
|
|
1545
1569
|
if (!v) return;
|
|
@@ -1574,12 +1598,29 @@ async function notifyLocalRecipientsOfNewMail(accountManager, toField, ccField,
|
|
|
1574
1598
|
}
|
|
1575
1599
|
if (!recipient || notified.has(recipient.id)) continue;
|
|
1576
1600
|
notified.add(recipient.id);
|
|
1601
|
+
let uid = 0;
|
|
1602
|
+
let lookup = "no-message-id";
|
|
1603
|
+
if (messageId) {
|
|
1604
|
+
try {
|
|
1605
|
+
const recipientPassword = getAgentPassword(recipient);
|
|
1606
|
+
const receiver = await getReceiver(
|
|
1607
|
+
recipient.stalwartPrincipal,
|
|
1608
|
+
recipientPassword,
|
|
1609
|
+
config
|
|
1610
|
+
);
|
|
1611
|
+
uid = await findUidByMessageId(receiver, messageId);
|
|
1612
|
+
lookup = uid > 0 ? "resolved" : "failed";
|
|
1613
|
+
} catch {
|
|
1614
|
+
lookup = "failed";
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1577
1617
|
pushEventToAgent(recipient.id, {
|
|
1578
1618
|
type: "new",
|
|
1579
|
-
|
|
1580
|
-
//
|
|
1581
|
-
//
|
|
1582
|
-
|
|
1619
|
+
uid,
|
|
1620
|
+
// Tell consumers whether the UID is real or a sentinel — preserves
|
|
1621
|
+
// backwards compat (uid is still always a number) while giving
|
|
1622
|
+
// clients a reliable signal to fall back to /mail/inbox.
|
|
1623
|
+
uidLookup: lookup,
|
|
1583
1624
|
internal: true,
|
|
1584
1625
|
from: { name: fromAgent.name, address: fromAgent.email },
|
|
1585
1626
|
subject,
|
|
@@ -1726,7 +1767,8 @@ function createMailRoutes(accountManager, config, db, gatewayManager) {
|
|
|
1726
1767
|
bcc,
|
|
1727
1768
|
agent,
|
|
1728
1769
|
subject,
|
|
1729
|
-
result.messageId
|
|
1770
|
+
result.messageId,
|
|
1771
|
+
config
|
|
1730
1772
|
).catch((err) => {
|
|
1731
1773
|
console.warn(`[mail] Internal SSE notify failed: ${err.message}`);
|
|
1732
1774
|
});
|
|
@@ -1988,19 +2030,19 @@ function createMailRoutes(accountManager, config, db, gatewayManager) {
|
|
|
1988
2030
|
router.post("/mail/folders", requireAgent, async (req, res, next) => {
|
|
1989
2031
|
try {
|
|
1990
2032
|
const agent = req.agent;
|
|
1991
|
-
const { name
|
|
1992
|
-
if (!
|
|
2033
|
+
const { name } = req.body || {};
|
|
2034
|
+
if (!name || typeof name !== "string" || !name.trim()) {
|
|
1993
2035
|
res.status(400).json({ error: "name is required" });
|
|
1994
2036
|
return;
|
|
1995
2037
|
}
|
|
1996
|
-
if (
|
|
2038
|
+
if (name.length > 200 || /[\\*%]/.test(name)) {
|
|
1997
2039
|
res.status(400).json({ error: "Invalid folder name" });
|
|
1998
2040
|
return;
|
|
1999
2041
|
}
|
|
2000
2042
|
const password = getAgentPassword(agent);
|
|
2001
2043
|
const receiver = await getReceiver(agent.stalwartPrincipal, password, config);
|
|
2002
|
-
await receiver.createFolder(
|
|
2003
|
-
res.json({ ok: true, folder:
|
|
2044
|
+
await receiver.createFolder(name);
|
|
2045
|
+
res.json({ ok: true, folder: name });
|
|
2004
2046
|
} catch (err) {
|
|
2005
2047
|
next(err);
|
|
2006
2048
|
}
|
|
@@ -2336,7 +2378,8 @@ function createMailRoutes(accountManager, config, db, gatewayManager) {
|
|
|
2336
2378
|
mailOpts.bcc,
|
|
2337
2379
|
agent,
|
|
2338
2380
|
mailOpts.subject,
|
|
2339
|
-
result.messageId
|
|
2381
|
+
result.messageId,
|
|
2382
|
+
config
|
|
2340
2383
|
).catch((err) => {
|
|
2341
2384
|
console.warn(`[mail] Internal SSE notify (approve) failed: ${err.message}`);
|
|
2342
2385
|
});
|
|
@@ -3478,7 +3521,7 @@ function buildColumnDDL(col, dialect) {
|
|
|
3478
3521
|
if (typeof col.default === "string") {
|
|
3479
3522
|
const trimmed = col.default.trim();
|
|
3480
3523
|
const isSqlExpr = /\(.*\)/.test(trimmed) || /^CURRENT_(?:TIMESTAMP|DATE|TIME)$/i.test(trimmed);
|
|
3481
|
-
val = isSqlExpr ? trimmed : `'${col.default.replace(/'/g, "''")}'`;
|
|
3524
|
+
val = isSqlExpr ? `(${trimmed})` : `'${col.default.replace(/'/g, "''")}'`;
|
|
3482
3525
|
} else {
|
|
3483
3526
|
val = col.default;
|
|
3484
3527
|
}
|
|
@@ -3491,15 +3534,15 @@ function buildColumnDDL(col, dialect) {
|
|
|
3491
3534
|
}
|
|
3492
3535
|
return ddl;
|
|
3493
3536
|
}
|
|
3494
|
-
function safeTableName(agentId,
|
|
3495
|
-
const clean =
|
|
3537
|
+
function safeTableName(agentId, name, shared) {
|
|
3538
|
+
const clean = name.replace(/[^a-zA-Z0-9_]/g, "").substring(0, 64);
|
|
3496
3539
|
if (!clean) throw new Error("Invalid table name");
|
|
3497
3540
|
const prefix = shared ? "shared" : `agt_${agentId.replace(/[^a-zA-Z0-9]/g, "").substring(0, 16)}`;
|
|
3498
3541
|
return `${prefix}_${clean}`;
|
|
3499
3542
|
}
|
|
3500
|
-
function resolveTable(agentId,
|
|
3501
|
-
if (
|
|
3502
|
-
return safeTableName(agentId,
|
|
3543
|
+
function resolveTable(agentId, name) {
|
|
3544
|
+
if (name.startsWith("agt_") || name.startsWith("shared_")) return name;
|
|
3545
|
+
return safeTableName(agentId, name, false);
|
|
3503
3546
|
}
|
|
3504
3547
|
function isSafeTable(tableName) {
|
|
3505
3548
|
return tableName.startsWith("agt_") || tableName.startsWith("shared_");
|
|
@@ -3657,8 +3700,8 @@ function createStorageRoutes(rawDb, accountManager, config, dialect = "sqlite")
|
|
|
3657
3700
|
if (!agent) return;
|
|
3658
3701
|
await ensureMetaTable();
|
|
3659
3702
|
try {
|
|
3660
|
-
const { name
|
|
3661
|
-
if (!
|
|
3703
|
+
const { name, columns, indexes, shared, description, timestamps } = req.body;
|
|
3704
|
+
if (!name || !columns?.length) return res.status(400).json({ error: "name and columns are required" });
|
|
3662
3705
|
const hasPK = columns.some((c) => c.primaryKey);
|
|
3663
3706
|
const allCols = [...hasPK ? [] : [{ name: "id", type: "text", primaryKey: true }], ...columns];
|
|
3664
3707
|
if (timestamps !== false) {
|
|
@@ -3669,9 +3712,9 @@ function createStorageRoutes(rawDb, accountManager, config, dialect = "sqlite")
|
|
|
3669
3712
|
allCols.push({ name: "updated_at", type: "timestamp", default: nowExpr(dialect) });
|
|
3670
3713
|
}
|
|
3671
3714
|
}
|
|
3672
|
-
const tableName = safeTableName(agent.id,
|
|
3715
|
+
const tableName = safeTableName(agent.id, name, !!shared);
|
|
3673
3716
|
const existing = await db.get("SELECT table_name FROM agenticmail_storage_meta WHERE table_name = ?", [tableName]);
|
|
3674
|
-
if (existing) return res.status(409).json({ error: `Table "${
|
|
3717
|
+
if (existing) return res.status(409).json({ error: `Table "${name}" already exists`, table: tableName });
|
|
3675
3718
|
const colDefs = allCols.map((c) => buildColumnDDL(c, dialect)).join(",\n ");
|
|
3676
3719
|
await db.run(`CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
3677
3720
|
${colDefs}
|
|
@@ -3692,7 +3735,7 @@ function createStorageRoutes(rawDb, accountManager, config, dialect = "sqlite")
|
|
|
3692
3735
|
}
|
|
3693
3736
|
await db.run(
|
|
3694
3737
|
"INSERT INTO agenticmail_storage_meta (table_name, agent_id, display_name, description, shared, columns, indexes) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
3695
|
-
[tableName, agent.id,
|
|
3738
|
+
[tableName, agent.id, name, description || "", shared ? 1 : 0, JSON.stringify(allCols), JSON.stringify(idxMeta)]
|
|
3696
3739
|
);
|
|
3697
3740
|
res.json({ ok: true, table: tableName, columns: allCols, indexes: idxMeta });
|
|
3698
3741
|
} catch (err) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agenticmail/api",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.61",
|
|
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.
|
|
30
|
+
"@agenticmail/core": "^0.5.61",
|
|
31
31
|
"cors": "^2.8.5",
|
|
32
32
|
"dotenv": "^16.4.7",
|
|
33
33
|
"express": "^4.21.0",
|