agent-mailbox-cli 0.1.0
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/api/auth.d.ts +5 -0
- package/dist/api/auth.d.ts.map +1 -0
- package/dist/api/auth.js +25 -0
- package/dist/api/auth.js.map +1 -0
- package/dist/api/routes/accounts.d.ts +3 -0
- package/dist/api/routes/accounts.d.ts.map +1 -0
- package/dist/api/routes/accounts.js +49 -0
- package/dist/api/routes/accounts.js.map +1 -0
- package/dist/api/routes/messages.d.ts +3 -0
- package/dist/api/routes/messages.d.ts.map +1 -0
- package/dist/api/routes/messages.js +205 -0
- package/dist/api/routes/messages.js.map +1 -0
- package/dist/api/routes/orgs.d.ts +3 -0
- package/dist/api/routes/orgs.d.ts.map +1 -0
- package/dist/api/routes/orgs.js +20 -0
- package/dist/api/routes/orgs.js.map +1 -0
- package/dist/api/server.d.ts +5 -0
- package/dist/api/server.d.ts.map +1 -0
- package/dist/api/server.js +31 -0
- package/dist/api/server.js.map +1 -0
- package/dist/api-server.d.ts +2 -0
- package/dist/api-server.d.ts.map +1 -0
- package/dist/api-server.js +21 -0
- package/dist/api-server.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +150 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +20 -0
- package/dist/config.js.map +1 -0
- package/dist/db/index.d.ts +3 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +12 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/init.d.ts +2 -0
- package/dist/db/init.d.ts.map +1 -0
- package/dist/db/init.js +16 -0
- package/dist/db/init.js.map +1 -0
- package/dist/db/schema.sql +38 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/smtp/receiver.d.ts +5 -0
- package/dist/smtp/receiver.d.ts.map +1 -0
- package/dist/smtp/receiver.js +88 -0
- package/dist/smtp/receiver.js.map +1 -0
- package/dist/smtp/sender.d.ts +11 -0
- package/dist/smtp/sender.d.ts.map +1 -0
- package/dist/smtp/sender.js +35 -0
- package/dist/smtp/sender.js.map +1 -0
- package/dist/smtp-server.d.ts +2 -0
- package/dist/smtp-server.d.ts.map +1 -0
- package/dist/smtp-server.js +21 -0
- package/dist/smtp-server.js.map +1 -0
- package/llms.txt +109 -0
- package/package.json +52 -0
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { FastifyRequest, FastifyReply } from 'fastify';
|
|
2
|
+
export declare function hashApiKey(key: string): string;
|
|
3
|
+
export declare function generateApiKey(): string;
|
|
4
|
+
export declare function authenticate(request: FastifyRequest, reply: FastifyReply): Promise<undefined>;
|
|
5
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/api/auth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAGvD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,wBAAsB,YAAY,CAChC,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,YAAY,sBAyBpB"}
|
package/dist/api/auth.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import { pool } from '../db/index.js';
|
|
3
|
+
export function hashApiKey(key) {
|
|
4
|
+
return crypto.createHash('sha256').update(key).digest('hex');
|
|
5
|
+
}
|
|
6
|
+
export function generateApiKey() {
|
|
7
|
+
return 'ae_' + crypto.randomBytes(24).toString('hex');
|
|
8
|
+
}
|
|
9
|
+
export async function authenticate(request, reply) {
|
|
10
|
+
const header = request.headers['authorization'] || request.headers['x-api-key'];
|
|
11
|
+
if (!header) {
|
|
12
|
+
return reply.code(401).send({ error: 'Missing API key' });
|
|
13
|
+
}
|
|
14
|
+
const key = typeof header === 'string' ? header.replace(/^Bearer\s+/i, '') : '';
|
|
15
|
+
if (!key) {
|
|
16
|
+
return reply.code(401).send({ error: 'Invalid API key' });
|
|
17
|
+
}
|
|
18
|
+
const hash = hashApiKey(key);
|
|
19
|
+
const result = await pool.query('SELECT id, name FROM orgs WHERE api_key_hash = $1', [hash]);
|
|
20
|
+
if (result.rows.length === 0) {
|
|
21
|
+
return reply.code(401).send({ error: 'Invalid API key' });
|
|
22
|
+
}
|
|
23
|
+
request.org = result.rows[0];
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/api/auth.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEtC,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,OAAuB,EACvB,KAAmB;IAEnB,MAAM,MAAM,GACV,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACnE,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,GAAG,GACP,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACtE,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAC7B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B,mDAAmD,EACnD,CAAC,IAAI,CAAC,CACP,CAAC;IAEF,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;IAC5D,CAAC;IAEA,OAAe,CAAC,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accounts.d.ts","sourceRoot":"","sources":["../../../src/api/routes/accounts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAK1C,wBAAsB,aAAa,CAAC,OAAO,EAAE,eAAe,iBAkE3D"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { pool } from '../../db/index.js';
|
|
2
|
+
import { authenticate } from '../auth.js';
|
|
3
|
+
import { config } from '../../config.js';
|
|
4
|
+
export async function accountRoutes(fastify) {
|
|
5
|
+
fastify.addHook('onRequest', authenticate);
|
|
6
|
+
// Create account
|
|
7
|
+
fastify.post('/v1/accounts', async (request, reply) => {
|
|
8
|
+
const org = request.org;
|
|
9
|
+
const { local_part, display_name } = request.body || {};
|
|
10
|
+
if (!local_part) {
|
|
11
|
+
return reply.code(400).send({ error: 'local_part is required' });
|
|
12
|
+
}
|
|
13
|
+
if (!/^[a-z0-9][a-z0-9._-]*[a-z0-9]$/i.test(local_part) && local_part.length > 1) {
|
|
14
|
+
return reply
|
|
15
|
+
.code(400)
|
|
16
|
+
.send({ error: 'Invalid local_part. Use letters, numbers, dots, hyphens, underscores.' });
|
|
17
|
+
}
|
|
18
|
+
const address = `${local_part.toLowerCase()}@${config.smtp.domain}`;
|
|
19
|
+
try {
|
|
20
|
+
const result = await pool.query(`INSERT INTO accounts (org_id, address, display_name)
|
|
21
|
+
VALUES ($1, $2, $3)
|
|
22
|
+
RETURNING id, address, display_name, created_at`, [org.id, address, display_name || null]);
|
|
23
|
+
return reply.code(201).send({ account: result.rows[0] });
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
if (err.code === '23505') {
|
|
27
|
+
return reply.code(409).send({ error: 'Address already taken' });
|
|
28
|
+
}
|
|
29
|
+
throw err;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
// List accounts
|
|
33
|
+
fastify.get('/v1/accounts', async (request) => {
|
|
34
|
+
const org = request.org;
|
|
35
|
+
const result = await pool.query('SELECT id, address, display_name, created_at FROM accounts WHERE org_id = $1 ORDER BY created_at DESC', [org.id]);
|
|
36
|
+
return { accounts: result.rows };
|
|
37
|
+
});
|
|
38
|
+
// Delete account
|
|
39
|
+
fastify.delete('/v1/accounts/:address', async (request, reply) => {
|
|
40
|
+
const org = request.org;
|
|
41
|
+
const { address } = request.params;
|
|
42
|
+
const result = await pool.query('DELETE FROM accounts WHERE address = $1 AND org_id = $2 RETURNING id', [address, org.id]);
|
|
43
|
+
if (result.rows.length === 0) {
|
|
44
|
+
return reply.code(404).send({ error: 'Account not found' });
|
|
45
|
+
}
|
|
46
|
+
return { deleted: true };
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=accounts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accounts.js","sourceRoot":"","sources":["../../../src/api/routes/accounts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../../src/api/routes/messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AA4B1C,wBAAsB,aAAa,CAAC,OAAO,EAAE,eAAe,iBAoQ3D"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { pool } from '../../db/index.js';
|
|
2
|
+
import { authenticate } from '../auth.js';
|
|
3
|
+
import { sendEmail } from '../../smtp/sender.js';
|
|
4
|
+
function formatMessage(row) {
|
|
5
|
+
return {
|
|
6
|
+
id: row.id,
|
|
7
|
+
message_id: row.message_id,
|
|
8
|
+
from: row.from_address,
|
|
9
|
+
to: row.to_address,
|
|
10
|
+
subject: row.subject,
|
|
11
|
+
text: row.text_body,
|
|
12
|
+
html: row.html_body,
|
|
13
|
+
direction: row.direction,
|
|
14
|
+
received_at: row.received_at,
|
|
15
|
+
read_at: row.read_at,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
async function getAccountForOrg(address, orgId) {
|
|
19
|
+
const result = await pool.query('SELECT id FROM accounts WHERE address = $1 AND org_id = $2', [address, orgId]);
|
|
20
|
+
return result.rows[0] || null;
|
|
21
|
+
}
|
|
22
|
+
export async function messageRoutes(fastify) {
|
|
23
|
+
fastify.addHook('onRequest', authenticate);
|
|
24
|
+
// List messages (inbox)
|
|
25
|
+
fastify.get('/v1/accounts/:address/messages', async (request, reply) => {
|
|
26
|
+
const org = request.org;
|
|
27
|
+
const { address } = request.params;
|
|
28
|
+
const limit = Math.min(parseInt(request.query.limit || '50', 10), 100);
|
|
29
|
+
const offset = parseInt(request.query.offset || '0', 10);
|
|
30
|
+
const since = request.query.since;
|
|
31
|
+
const account = await getAccountForOrg(address, org.id);
|
|
32
|
+
if (!account)
|
|
33
|
+
return reply.code(404).send({ error: 'Account not found' });
|
|
34
|
+
let query = `SELECT * FROM messages WHERE account_id = $1`;
|
|
35
|
+
const params = [account.id];
|
|
36
|
+
if (since) {
|
|
37
|
+
params.push(since);
|
|
38
|
+
query += ` AND received_at > $${params.length}`;
|
|
39
|
+
}
|
|
40
|
+
query += ` ORDER BY received_at DESC`;
|
|
41
|
+
params.push(limit);
|
|
42
|
+
query += ` LIMIT $${params.length}`;
|
|
43
|
+
params.push(offset);
|
|
44
|
+
query += ` OFFSET $${params.length}`;
|
|
45
|
+
const result = await pool.query(query, params);
|
|
46
|
+
return {
|
|
47
|
+
messages: result.rows.map(formatMessage),
|
|
48
|
+
count: result.rows.length,
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
// Read single message
|
|
52
|
+
fastify.get('/v1/accounts/:address/messages/:messageId', async (request, reply) => {
|
|
53
|
+
const org = request.org;
|
|
54
|
+
const { address, messageId } = request.params;
|
|
55
|
+
const account = await getAccountForOrg(address, org.id);
|
|
56
|
+
if (!account)
|
|
57
|
+
return reply.code(404).send({ error: 'Account not found' });
|
|
58
|
+
const result = await pool.query('SELECT * FROM messages WHERE id = $1 AND account_id = $2', [messageId, account.id]);
|
|
59
|
+
if (result.rows.length === 0) {
|
|
60
|
+
return reply.code(404).send({ error: 'Message not found' });
|
|
61
|
+
}
|
|
62
|
+
// Mark as read
|
|
63
|
+
if (!result.rows[0].read_at) {
|
|
64
|
+
await pool.query('UPDATE messages SET read_at = NOW() WHERE id = $1', [
|
|
65
|
+
messageId,
|
|
66
|
+
]);
|
|
67
|
+
}
|
|
68
|
+
return { message: formatMessage(result.rows[0]) };
|
|
69
|
+
});
|
|
70
|
+
// Wait for new message (long poll using LISTEN/NOTIFY)
|
|
71
|
+
fastify.get('/v1/accounts/:address/messages/wait', async (request, reply) => {
|
|
72
|
+
const org = request.org;
|
|
73
|
+
const { address } = request.params;
|
|
74
|
+
const timeout = Math.min(parseInt(request.query.timeout || '30', 10), 120);
|
|
75
|
+
const since = request.query.since;
|
|
76
|
+
const account = await getAccountForOrg(address, org.id);
|
|
77
|
+
if (!account)
|
|
78
|
+
return reply.code(404).send({ error: 'Account not found' });
|
|
79
|
+
// Check if there's already a new message
|
|
80
|
+
if (since) {
|
|
81
|
+
const existing = await pool.query('SELECT * FROM messages WHERE account_id = $1 AND received_at > $2 ORDER BY received_at ASC LIMIT 1', [account.id, since]);
|
|
82
|
+
if (existing.rows.length) {
|
|
83
|
+
return { message: formatMessage(existing.rows[0]) };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Listen for new messages via pg LISTEN/NOTIFY
|
|
87
|
+
const client = await pool.connect();
|
|
88
|
+
try {
|
|
89
|
+
await client.query('LISTEN new_message');
|
|
90
|
+
const message = await new Promise((resolve) => {
|
|
91
|
+
const timer = setTimeout(() => resolve(null), timeout * 1000);
|
|
92
|
+
const onNotification = async (msg) => {
|
|
93
|
+
if (msg.payload === account.id) {
|
|
94
|
+
clearTimeout(timer);
|
|
95
|
+
client.removeListener('notification', onNotification);
|
|
96
|
+
const result = await pool.query('SELECT * FROM messages WHERE account_id = $1 ORDER BY received_at DESC LIMIT 1', [account.id]);
|
|
97
|
+
resolve(result.rows[0] || null);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
client.on('notification', onNotification);
|
|
101
|
+
});
|
|
102
|
+
if (message) {
|
|
103
|
+
return { message: formatMessage(message) };
|
|
104
|
+
}
|
|
105
|
+
return reply.code(204).send();
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
client.query('UNLISTEN new_message').catch(() => { });
|
|
109
|
+
client.release();
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
// Reply to message
|
|
113
|
+
fastify.post('/v1/accounts/:address/messages/:messageId/reply', async (request, reply) => {
|
|
114
|
+
const org = request.org;
|
|
115
|
+
const { address, messageId } = request.params;
|
|
116
|
+
const { text, html } = request.body || {};
|
|
117
|
+
if (!text && !html) {
|
|
118
|
+
return reply
|
|
119
|
+
.code(400)
|
|
120
|
+
.send({ error: 'text or html body is required' });
|
|
121
|
+
}
|
|
122
|
+
const account = await getAccountForOrg(address, org.id);
|
|
123
|
+
if (!account)
|
|
124
|
+
return reply.code(404).send({ error: 'Account not found' });
|
|
125
|
+
const original = await pool.query('SELECT * FROM messages WHERE id = $1 AND account_id = $2', [messageId, account.id]);
|
|
126
|
+
if (original.rows.length === 0) {
|
|
127
|
+
return reply.code(404).send({ error: 'Message not found' });
|
|
128
|
+
}
|
|
129
|
+
const orig = original.rows[0];
|
|
130
|
+
const subject = orig.subject?.startsWith('Re:')
|
|
131
|
+
? orig.subject
|
|
132
|
+
: `Re: ${orig.subject}`;
|
|
133
|
+
const info = await sendEmail({
|
|
134
|
+
from: address,
|
|
135
|
+
to: orig.from_address,
|
|
136
|
+
subject,
|
|
137
|
+
text: text || undefined,
|
|
138
|
+
html: html || undefined,
|
|
139
|
+
inReplyTo: orig.message_id || undefined,
|
|
140
|
+
references: orig.message_id || undefined,
|
|
141
|
+
});
|
|
142
|
+
// Store outbound copy
|
|
143
|
+
await pool.query(`INSERT INTO messages
|
|
144
|
+
(account_id, message_id, in_reply_to, from_address, to_address, subject, text_body, html_body, direction)
|
|
145
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 'outbound')`, [
|
|
146
|
+
account.id,
|
|
147
|
+
info.messageId,
|
|
148
|
+
orig.message_id,
|
|
149
|
+
address,
|
|
150
|
+
orig.from_address,
|
|
151
|
+
subject,
|
|
152
|
+
text || null,
|
|
153
|
+
html || null,
|
|
154
|
+
]);
|
|
155
|
+
return reply.code(201).send({
|
|
156
|
+
sent: true,
|
|
157
|
+
message_id: info.messageId,
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
// Delete message
|
|
161
|
+
fastify.delete('/v1/accounts/:address/messages/:messageId', async (request, reply) => {
|
|
162
|
+
const org = request.org;
|
|
163
|
+
const { address, messageId } = request.params;
|
|
164
|
+
const account = await getAccountForOrg(address, org.id);
|
|
165
|
+
if (!account)
|
|
166
|
+
return reply.code(404).send({ error: 'Account not found' });
|
|
167
|
+
const result = await pool.query('DELETE FROM messages WHERE id = $1 AND account_id = $2 RETURNING id', [messageId, account.id]);
|
|
168
|
+
if (result.rows.length === 0) {
|
|
169
|
+
return reply.code(404).send({ error: 'Message not found' });
|
|
170
|
+
}
|
|
171
|
+
return { deleted: true };
|
|
172
|
+
});
|
|
173
|
+
// Send new email (not a reply)
|
|
174
|
+
fastify.post('/v1/accounts/:address/send', async (request, reply) => {
|
|
175
|
+
const org = request.org;
|
|
176
|
+
const { address } = request.params;
|
|
177
|
+
const { to, subject, text, html } = request.body || {};
|
|
178
|
+
if (!to || !subject) {
|
|
179
|
+
return reply
|
|
180
|
+
.code(400)
|
|
181
|
+
.send({ error: 'to and subject are required' });
|
|
182
|
+
}
|
|
183
|
+
if (!text && !html) {
|
|
184
|
+
return reply.code(400).send({ error: 'text or html body is required' });
|
|
185
|
+
}
|
|
186
|
+
const account = await getAccountForOrg(address, org.id);
|
|
187
|
+
if (!account)
|
|
188
|
+
return reply.code(404).send({ error: 'Account not found' });
|
|
189
|
+
const info = await sendEmail({
|
|
190
|
+
from: address,
|
|
191
|
+
to,
|
|
192
|
+
subject,
|
|
193
|
+
text: text || undefined,
|
|
194
|
+
html: html || undefined,
|
|
195
|
+
});
|
|
196
|
+
await pool.query(`INSERT INTO messages
|
|
197
|
+
(account_id, message_id, from_address, to_address, subject, text_body, html_body, direction)
|
|
198
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, 'outbound')`, [account.id, info.messageId, address, to, subject, text || null, html || null]);
|
|
199
|
+
return reply.code(201).send({
|
|
200
|
+
sent: true,
|
|
201
|
+
message_id: info.messageId,
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../../src/api/routes/messages.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orgs.d.ts","sourceRoot":"","sources":["../../../src/api/routes/orgs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAI1C,wBAAsB,SAAS,CAAC,OAAO,EAAE,eAAe,iBAwBvD"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { pool } from '../../db/index.js';
|
|
2
|
+
import { generateApiKey, hashApiKey } from '../auth.js';
|
|
3
|
+
export async function orgRoutes(fastify) {
|
|
4
|
+
// Create org — this is the only unauthenticated endpoint
|
|
5
|
+
fastify.post('/v1/orgs', async (request, reply) => {
|
|
6
|
+
const { name } = request.body || {};
|
|
7
|
+
if (!name) {
|
|
8
|
+
return reply.code(400).send({ error: 'name is required' });
|
|
9
|
+
}
|
|
10
|
+
const apiKey = generateApiKey();
|
|
11
|
+
const hash = hashApiKey(apiKey);
|
|
12
|
+
const result = await pool.query('INSERT INTO orgs (name, api_key_hash) VALUES ($1, $2) RETURNING id, name, created_at', [name, hash]);
|
|
13
|
+
return reply.code(201).send({
|
|
14
|
+
org: result.rows[0],
|
|
15
|
+
api_key: apiKey,
|
|
16
|
+
note: 'Save this API key — it cannot be retrieved again.',
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=orgs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orgs.js","sourceRoot":"","sources":["../../../src/api/routes/orgs.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExD,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAwB;IACtD,yDAAyD;IACzD,OAAO,CAAC,IAAI,CAET,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QACtC,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC;QACpC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC7D,CAAC;QAED,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;QAEhC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B,sFAAsF,EACtF,CAAC,IAAI,EAAE,IAAI,CAAC,CACb,CAAC;QAEF,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YAC1B,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;YACnB,OAAO,EAAE,MAAM;YACf,IAAI,EAAE,mDAAmD;SAC1D,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/api/server.ts"],"names":[],"mappings":"AAOA,wBAAsB,eAAe;;;GA2BpC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import Fastify from 'fastify';
|
|
2
|
+
import cors from '@fastify/cors';
|
|
3
|
+
import { config } from '../config.js';
|
|
4
|
+
import { orgRoutes } from './routes/orgs.js';
|
|
5
|
+
import { accountRoutes } from './routes/accounts.js';
|
|
6
|
+
import { messageRoutes } from './routes/messages.js';
|
|
7
|
+
export async function createApiServer() {
|
|
8
|
+
const fastify = Fastify({
|
|
9
|
+
logger: true,
|
|
10
|
+
});
|
|
11
|
+
await fastify.register(cors);
|
|
12
|
+
// Health check
|
|
13
|
+
fastify.get('/health', async () => ({ status: 'ok' }));
|
|
14
|
+
// Routes
|
|
15
|
+
await fastify.register(orgRoutes);
|
|
16
|
+
await fastify.register(accountRoutes);
|
|
17
|
+
await fastify.register(messageRoutes);
|
|
18
|
+
return {
|
|
19
|
+
async start() {
|
|
20
|
+
await fastify.listen({
|
|
21
|
+
port: config.api.port,
|
|
22
|
+
host: config.api.host,
|
|
23
|
+
});
|
|
24
|
+
console.log(`API server listening on ${config.api.host}:${config.api.port}`);
|
|
25
|
+
},
|
|
26
|
+
async stop() {
|
|
27
|
+
await fastify.close();
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/api/server.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,IAAI,MAAM,eAAe,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,MAAM,OAAO,GAAG,OAAO,CAAC;QACtB,MAAM,EAAE,IAAI;KACb,CAAC,CAAC;IAEH,MAAM,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAE7B,eAAe;IACf,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAEvD,SAAS;IACT,MAAM,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IAClC,MAAM,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;IACtC,MAAM,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;IAEtC,OAAO;QACL,KAAK,CAAC,KAAK;YACT,MAAM,OAAO,CAAC,MAAM,CAAC;gBACnB,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI;gBACrB,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI;aACtB,CAAC,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,2BAA2B,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QAC/E,CAAC;QACD,KAAK,CAAC,IAAI;YACR,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-server.d.ts","sourceRoot":"","sources":["../src/api-server.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createApiServer } from './api/server.js';
|
|
2
|
+
import { pool } from './db/index.js';
|
|
3
|
+
async function main() {
|
|
4
|
+
console.log('Starting AgentEmail API...');
|
|
5
|
+
const api = await createApiServer();
|
|
6
|
+
await api.start();
|
|
7
|
+
console.log('AgentEmail API is running.');
|
|
8
|
+
const shutdown = async () => {
|
|
9
|
+
console.log('\nShutting down API...');
|
|
10
|
+
await api.stop();
|
|
11
|
+
await pool.end();
|
|
12
|
+
process.exit(0);
|
|
13
|
+
};
|
|
14
|
+
process.on('SIGINT', shutdown);
|
|
15
|
+
process.on('SIGTERM', shutdown);
|
|
16
|
+
}
|
|
17
|
+
main().catch((err) => {
|
|
18
|
+
console.error('Fatal error:', err);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
});
|
|
21
|
+
//# sourceMappingURL=api-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-server.js","sourceRoot":"","sources":["../src/api-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAErC,KAAK,UAAU,IAAI;IACjB,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;IAC1C,MAAM,GAAG,GAAG,MAAM,eAAe,EAAE,CAAC;IACpC,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;IAClB,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;IAE1C,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE;QAC1B,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;QACtC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QACjB,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;QACjB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;IACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
const program = new Command();
|
|
4
|
+
const API_URL = process.env.AGENT_EMAIL_API_URL || 'https://agent-email-api-production.up.railway.app';
|
|
5
|
+
const API_KEY = process.env.AGENT_EMAIL_API_KEY || '';
|
|
6
|
+
async function api(method, path, body) {
|
|
7
|
+
const headers = {
|
|
8
|
+
'Content-Type': 'application/json',
|
|
9
|
+
};
|
|
10
|
+
if (API_KEY) {
|
|
11
|
+
headers['Authorization'] = `Bearer ${API_KEY}`;
|
|
12
|
+
}
|
|
13
|
+
const res = await fetch(`${API_URL}${path}`, {
|
|
14
|
+
method,
|
|
15
|
+
headers,
|
|
16
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
17
|
+
});
|
|
18
|
+
if (res.status === 204)
|
|
19
|
+
return null;
|
|
20
|
+
const data = await res.json();
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
console.error(`Error ${res.status}:`, data.error || data);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
return data;
|
|
26
|
+
}
|
|
27
|
+
program
|
|
28
|
+
.name('agentmailbox')
|
|
29
|
+
.description('CLI for AgentEmail service')
|
|
30
|
+
.version('0.1.0');
|
|
31
|
+
// Org commands
|
|
32
|
+
const org = program.command('org');
|
|
33
|
+
org
|
|
34
|
+
.command('create')
|
|
35
|
+
.argument('<name>', 'Organization name')
|
|
36
|
+
.action(async (name) => {
|
|
37
|
+
const data = await api('POST', '/v1/orgs', { name });
|
|
38
|
+
console.log('Organization created:');
|
|
39
|
+
console.log(` ID: ${data.org.id}`);
|
|
40
|
+
console.log(` Name: ${data.org.name}`);
|
|
41
|
+
console.log(` API Key: ${data.api_key}`);
|
|
42
|
+
console.log('');
|
|
43
|
+
console.log('Save this API key — it cannot be retrieved again.');
|
|
44
|
+
console.log(`Export it: export AGENT_EMAIL_API_KEY=${data.api_key}`);
|
|
45
|
+
});
|
|
46
|
+
// Account commands
|
|
47
|
+
const account = program.command('account');
|
|
48
|
+
account
|
|
49
|
+
.command('create')
|
|
50
|
+
.argument('<local_part>', 'Email local part (before @)')
|
|
51
|
+
.option('-d, --display-name <name>', 'Display name')
|
|
52
|
+
.action(async (localPart, opts) => {
|
|
53
|
+
const data = await api('POST', '/v1/accounts', {
|
|
54
|
+
local_part: localPart,
|
|
55
|
+
display_name: opts.displayName,
|
|
56
|
+
});
|
|
57
|
+
console.log(`Account created: ${data.account.address}`);
|
|
58
|
+
});
|
|
59
|
+
account
|
|
60
|
+
.command('list')
|
|
61
|
+
.action(async () => {
|
|
62
|
+
const data = await api('GET', '/v1/accounts');
|
|
63
|
+
if (data.accounts.length === 0) {
|
|
64
|
+
console.log('No accounts.');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
for (const a of data.accounts) {
|
|
68
|
+
console.log(` ${a.address} (${a.display_name || 'no name'}) created ${a.created_at}`);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
account
|
|
72
|
+
.command('delete')
|
|
73
|
+
.argument('<address>', 'Full email address')
|
|
74
|
+
.action(async (address) => {
|
|
75
|
+
await api('DELETE', `/v1/accounts/${encodeURIComponent(address)}`);
|
|
76
|
+
console.log(`Deleted ${address}`);
|
|
77
|
+
});
|
|
78
|
+
// Message commands
|
|
79
|
+
const msg = program.command('messages').alias('msg');
|
|
80
|
+
msg
|
|
81
|
+
.command('list')
|
|
82
|
+
.argument('<address>', 'Email address')
|
|
83
|
+
.option('-l, --limit <n>', 'Max messages', '20')
|
|
84
|
+
.action(async (address, opts) => {
|
|
85
|
+
const data = await api('GET', `/v1/accounts/${encodeURIComponent(address)}/messages?limit=${opts.limit}`);
|
|
86
|
+
if (data.messages.length === 0) {
|
|
87
|
+
console.log('No messages.');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
for (const m of data.messages) {
|
|
91
|
+
const read = m.read_at ? ' ' : '*';
|
|
92
|
+
const dir = m.direction === 'outbound' ? '>>>' : '<<<';
|
|
93
|
+
console.log(`${read} ${m.id.slice(0, 8)} ${dir} ${m.from} → ${m.to} "${m.subject}" ${m.received_at}`);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
msg
|
|
97
|
+
.command('read')
|
|
98
|
+
.argument('<address>', 'Email address')
|
|
99
|
+
.argument('<id>', 'Message ID')
|
|
100
|
+
.action(async (address, id) => {
|
|
101
|
+
const data = await api('GET', `/v1/accounts/${encodeURIComponent(address)}/messages/${id}`);
|
|
102
|
+
const m = data.message;
|
|
103
|
+
console.log(`From: ${m.from}`);
|
|
104
|
+
console.log(`To: ${m.to}`);
|
|
105
|
+
console.log(`Subject: ${m.subject}`);
|
|
106
|
+
console.log(`Date: ${m.received_at}`);
|
|
107
|
+
console.log('---');
|
|
108
|
+
console.log(m.text || m.html || '(empty body)');
|
|
109
|
+
});
|
|
110
|
+
msg
|
|
111
|
+
.command('wait')
|
|
112
|
+
.argument('<address>', 'Email address')
|
|
113
|
+
.option('-t, --timeout <seconds>', 'Timeout in seconds', '60')
|
|
114
|
+
.option('-s, --since <timestamp>', 'Only messages after this timestamp')
|
|
115
|
+
.action(async (address, opts) => {
|
|
116
|
+
console.log(`Waiting for email to ${address}...`);
|
|
117
|
+
const params = new URLSearchParams({ timeout: opts.timeout });
|
|
118
|
+
if (opts.since)
|
|
119
|
+
params.set('since', opts.since);
|
|
120
|
+
const data = await api('GET', `/v1/accounts/${encodeURIComponent(address)}/messages/wait?${params}`);
|
|
121
|
+
if (!data) {
|
|
122
|
+
console.log('Timeout — no new messages.');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const m = data.message;
|
|
126
|
+
console.log(`New message from ${m.from}: "${m.subject}"`);
|
|
127
|
+
console.log('---');
|
|
128
|
+
console.log(m.text || m.html || '(empty body)');
|
|
129
|
+
});
|
|
130
|
+
msg
|
|
131
|
+
.command('reply')
|
|
132
|
+
.argument('<address>', 'Email address')
|
|
133
|
+
.argument('<id>', 'Message ID to reply to')
|
|
134
|
+
.argument('<body>', 'Reply text')
|
|
135
|
+
.action(async (address, id, body) => {
|
|
136
|
+
const data = await api('POST', `/v1/accounts/${encodeURIComponent(address)}/messages/${id}/reply`, { text: body });
|
|
137
|
+
console.log(`Reply sent. Message-ID: ${data.message_id}`);
|
|
138
|
+
});
|
|
139
|
+
msg
|
|
140
|
+
.command('send')
|
|
141
|
+
.argument('<from>', 'From address (your account)')
|
|
142
|
+
.argument('<to>', 'Recipient address')
|
|
143
|
+
.argument('<subject>', 'Subject line')
|
|
144
|
+
.argument('<body>', 'Email body')
|
|
145
|
+
.action(async (from, to, subject, body) => {
|
|
146
|
+
const data = await api('POST', `/v1/accounts/${encodeURIComponent(from)}/send`, { to, subject, text: body });
|
|
147
|
+
console.log(`Sent. Message-ID: ${data.message_id}`);
|
|
148
|
+
});
|
|
149
|
+
program.parse();
|
|
150
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
export declare const config: {
|
|
3
|
+
database: {
|
|
4
|
+
url: string;
|
|
5
|
+
};
|
|
6
|
+
smtp: {
|
|
7
|
+
port: number;
|
|
8
|
+
host: string;
|
|
9
|
+
domain: string;
|
|
10
|
+
};
|
|
11
|
+
api: {
|
|
12
|
+
port: number;
|
|
13
|
+
host: string;
|
|
14
|
+
};
|
|
15
|
+
dkim: {
|
|
16
|
+
selector: string;
|
|
17
|
+
privateKey: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AAEvB,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;CAiBlB,CAAC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
export const config = {
|
|
3
|
+
database: {
|
|
4
|
+
url: process.env.DATABASE_URL || 'postgres://localhost:5432/agentemail',
|
|
5
|
+
},
|
|
6
|
+
smtp: {
|
|
7
|
+
port: parseInt(process.env.SMTP_PORT || '2525', 10),
|
|
8
|
+
host: process.env.SMTP_HOST || '0.0.0.0',
|
|
9
|
+
domain: process.env.SMTP_DOMAIN || 'agentmailbox.io',
|
|
10
|
+
},
|
|
11
|
+
api: {
|
|
12
|
+
port: parseInt(process.env.API_PORT || '3025', 10),
|
|
13
|
+
host: process.env.API_HOST || '0.0.0.0',
|
|
14
|
+
},
|
|
15
|
+
dkim: {
|
|
16
|
+
selector: process.env.DKIM_SELECTOR || 'default',
|
|
17
|
+
privateKey: process.env.DKIM_PRIVATE_KEY || '',
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AAEvB,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,QAAQ,EAAE;QACR,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,sCAAsC;KACxE;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,MAAM,EAAE,EAAE,CAAC;QACnD,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,SAAS;QACxC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,iBAAiB;KACrD;IACD,GAAG,EAAE;QACH,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,MAAM,EAAE,EAAE,CAAC;QAClD,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,SAAS;KACxC;IACD,IAAI,EAAE;QACJ,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,SAAS;QAChD,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,EAAE;KAC/C;CACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/db/index.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,IAAI,mBAEf,CAAC;AAMH,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,0CAEvD"}
|
package/dist/db/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import pg from 'pg';
|
|
2
|
+
import { config } from '../config.js';
|
|
3
|
+
export const pool = new pg.Pool({
|
|
4
|
+
connectionString: config.database.url,
|
|
5
|
+
});
|
|
6
|
+
pool.on('error', (err) => {
|
|
7
|
+
console.error('Unexpected pool error:', err);
|
|
8
|
+
});
|
|
9
|
+
export async function query(text, params) {
|
|
10
|
+
return pool.query(text, params);
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/db/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC,MAAM,CAAC,MAAM,IAAI,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC;IAC9B,gBAAgB,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG;CACtC,CAAC,CAAC;AAEH,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;IACvB,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,GAAG,CAAC,CAAC;AAC/C,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,IAAY,EAAE,MAAc;IACtD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/db/init.ts"],"names":[],"mappings":""}
|
package/dist/db/init.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { pool } from './index.js';
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
async function init() {
|
|
7
|
+
const schema = fs.readFileSync(path.join(__dirname, 'schema.sql'), 'utf-8');
|
|
8
|
+
await pool.query(schema);
|
|
9
|
+
console.log('Database initialized.');
|
|
10
|
+
await pool.end();
|
|
11
|
+
}
|
|
12
|
+
init().catch((err) => {
|
|
13
|
+
console.error('Failed to initialize database:', err);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
});
|
|
16
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/db/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAElC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE/D,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;IAC5E,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACzB,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;IACrC,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;AACnB,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,GAAG,CAAC,CAAC;IACrD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
2
|
+
|
|
3
|
+
CREATE TABLE IF NOT EXISTS orgs (
|
|
4
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
5
|
+
name TEXT NOT NULL,
|
|
6
|
+
api_key_hash TEXT NOT NULL UNIQUE,
|
|
7
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
CREATE TABLE IF NOT EXISTS accounts (
|
|
11
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
12
|
+
org_id UUID NOT NULL REFERENCES orgs(id) ON DELETE CASCADE,
|
|
13
|
+
address TEXT NOT NULL UNIQUE,
|
|
14
|
+
display_name TEXT,
|
|
15
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
19
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
20
|
+
account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
|
|
21
|
+
message_id TEXT,
|
|
22
|
+
in_reply_to TEXT,
|
|
23
|
+
from_address TEXT NOT NULL,
|
|
24
|
+
to_address TEXT NOT NULL,
|
|
25
|
+
subject TEXT,
|
|
26
|
+
text_body TEXT,
|
|
27
|
+
html_body TEXT,
|
|
28
|
+
raw TEXT,
|
|
29
|
+
headers JSONB,
|
|
30
|
+
direction TEXT NOT NULL DEFAULT 'inbound',
|
|
31
|
+
received_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
32
|
+
read_at TIMESTAMPTZ
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
CREATE INDEX IF NOT EXISTS idx_messages_account_received ON messages(account_id, received_at DESC);
|
|
36
|
+
CREATE INDEX IF NOT EXISTS idx_accounts_org ON accounts(org_id);
|
|
37
|
+
CREATE INDEX IF NOT EXISTS idx_accounts_address ON accounts(address);
|
|
38
|
+
CREATE INDEX IF NOT EXISTS idx_orgs_api_key_hash ON orgs(api_key_hash);
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createSmtpReceiver } from './smtp/receiver.js';
|
|
2
|
+
import { createApiServer } from './api/server.js';
|
|
3
|
+
import { pool } from './db/index.js';
|
|
4
|
+
async function main() {
|
|
5
|
+
console.log('Starting AgentEmail...');
|
|
6
|
+
const smtp = createSmtpReceiver();
|
|
7
|
+
const api = await createApiServer();
|
|
8
|
+
await smtp.start();
|
|
9
|
+
await api.start();
|
|
10
|
+
console.log('AgentEmail is running.');
|
|
11
|
+
const shutdown = async () => {
|
|
12
|
+
console.log('\nShutting down...');
|
|
13
|
+
await api.stop();
|
|
14
|
+
await smtp.stop();
|
|
15
|
+
await pool.end();
|
|
16
|
+
process.exit(0);
|
|
17
|
+
};
|
|
18
|
+
process.on('SIGINT', shutdown);
|
|
19
|
+
process.on('SIGTERM', shutdown);
|
|
20
|
+
}
|
|
21
|
+
main().catch((err) => {
|
|
22
|
+
console.error('Fatal error:', err);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
});
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAErC,KAAK,UAAU,IAAI;IACjB,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IAEtC,MAAM,IAAI,GAAG,kBAAkB,EAAE,CAAC;IAClC,MAAM,GAAG,GAAG,MAAM,eAAe,EAAE,CAAC;IAEpC,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;IACnB,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;IAElB,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IAEtC,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE;QAC1B,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;QAClC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QACjB,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;QACjB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;IACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"receiver.d.ts","sourceRoot":"","sources":["../../src/smtp/receiver.ts"],"names":[],"mappings":"AAKA,wBAAgB,kBAAkB;;;EA2GjC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { SMTPServer } from 'smtp-server';
|
|
2
|
+
import { simpleParser } from 'mailparser';
|
|
3
|
+
import { pool } from '../db/index.js';
|
|
4
|
+
import { config } from '../config.js';
|
|
5
|
+
export function createSmtpReceiver() {
|
|
6
|
+
const domain = config.smtp.domain;
|
|
7
|
+
const server = new SMTPServer({
|
|
8
|
+
authOptional: true,
|
|
9
|
+
disabledCommands: ['AUTH'],
|
|
10
|
+
banner: `AgentEmail SMTP`,
|
|
11
|
+
size: 10 * 1024 * 1024, // 10MB max
|
|
12
|
+
onRcptTo(address, _session, callback) {
|
|
13
|
+
const addr = address.address.toLowerCase();
|
|
14
|
+
if (addr.endsWith(`@${domain}`)) {
|
|
15
|
+
callback();
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
callback(new Error(`Not accepting mail for ${addr}`));
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
onData(stream, session, callback) {
|
|
22
|
+
const chunks = [];
|
|
23
|
+
stream.on('data', (chunk) => chunks.push(chunk));
|
|
24
|
+
stream.on('end', async () => {
|
|
25
|
+
try {
|
|
26
|
+
const raw = Buffer.concat(chunks).toString('utf-8');
|
|
27
|
+
const parsed = await simpleParser(raw);
|
|
28
|
+
const fromAddress = session.envelope.mailFrom &&
|
|
29
|
+
typeof session.envelope.mailFrom === 'object'
|
|
30
|
+
? session.envelope.mailFrom.address
|
|
31
|
+
: '';
|
|
32
|
+
const recipients = session.envelope.rcptTo.map((r) => r.address.toLowerCase());
|
|
33
|
+
for (const toAddress of recipients) {
|
|
34
|
+
const result = await pool.query('SELECT id FROM accounts WHERE address = $1', [toAddress]);
|
|
35
|
+
if (result.rows.length === 0) {
|
|
36
|
+
console.log(`No account for ${toAddress}, skipping`);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const accountId = result.rows[0].id;
|
|
40
|
+
await pool.query(`INSERT INTO messages
|
|
41
|
+
(account_id, message_id, from_address, to_address, subject, text_body, html_body, raw, headers, direction)
|
|
42
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, 'inbound')`, [
|
|
43
|
+
accountId,
|
|
44
|
+
parsed.messageId || null,
|
|
45
|
+
fromAddress,
|
|
46
|
+
toAddress,
|
|
47
|
+
parsed.subject || null,
|
|
48
|
+
parsed.text || null,
|
|
49
|
+
parsed.html || null,
|
|
50
|
+
raw,
|
|
51
|
+
JSON.stringify(parsed.headerLines?.map((h) => ({
|
|
52
|
+
key: h.key,
|
|
53
|
+
line: h.line,
|
|
54
|
+
})) || []),
|
|
55
|
+
]);
|
|
56
|
+
// Notify listeners waiting for new messages
|
|
57
|
+
await pool.query(`SELECT pg_notify('new_message', $1)`, [
|
|
58
|
+
accountId,
|
|
59
|
+
]);
|
|
60
|
+
console.log(`Stored message for ${toAddress} from ${fromAddress}: ${parsed.subject}`);
|
|
61
|
+
}
|
|
62
|
+
callback();
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
console.error('Error processing email:', err);
|
|
66
|
+
callback(err);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
return {
|
|
72
|
+
start() {
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
server.listen(config.smtp.port, config.smtp.host, () => {
|
|
75
|
+
console.log(`SMTP receiver listening on ${config.smtp.host}:${config.smtp.port} for @${domain}`);
|
|
76
|
+
resolve();
|
|
77
|
+
});
|
|
78
|
+
server.on('error', reject);
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
stop() {
|
|
82
|
+
return new Promise((resolve) => {
|
|
83
|
+
server.close(() => resolve());
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=receiver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"receiver.js","sourceRoot":"","sources":["../../src/smtp/receiver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface SendOptions {
|
|
2
|
+
from: string;
|
|
3
|
+
to: string;
|
|
4
|
+
subject: string;
|
|
5
|
+
text?: string;
|
|
6
|
+
html?: string;
|
|
7
|
+
inReplyTo?: string;
|
|
8
|
+
references?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function sendEmail(opts: SendOptions): Promise<import("nodemailer/lib/smtp-pool/index.js").SentMessageInfo>;
|
|
11
|
+
//# sourceMappingURL=sender.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sender.d.ts","sourceRoot":"","sources":["../../src/smtp/sender.ts"],"names":[],"mappings":"AA0BA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,WAAW,wEAWhD"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import nodemailer from 'nodemailer';
|
|
2
|
+
import { config } from '../config.js';
|
|
3
|
+
const transportOptions = {
|
|
4
|
+
host: '127.0.0.1',
|
|
5
|
+
port: config.smtp.port,
|
|
6
|
+
secure: false,
|
|
7
|
+
tls: { rejectUnauthorized: false },
|
|
8
|
+
};
|
|
9
|
+
// Add DKIM signing if configured
|
|
10
|
+
const dkimOptions = config.dkim.privateKey
|
|
11
|
+
? {
|
|
12
|
+
dkim: {
|
|
13
|
+
domainName: config.smtp.domain,
|
|
14
|
+
keySelector: config.dkim.selector,
|
|
15
|
+
privateKey: config.dkim.privateKey,
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
: {};
|
|
19
|
+
const transporter = nodemailer.createTransport({
|
|
20
|
+
...transportOptions,
|
|
21
|
+
...dkimOptions,
|
|
22
|
+
});
|
|
23
|
+
export async function sendEmail(opts) {
|
|
24
|
+
const info = await transporter.sendMail({
|
|
25
|
+
from: opts.from,
|
|
26
|
+
to: opts.to,
|
|
27
|
+
subject: opts.subject,
|
|
28
|
+
text: opts.text,
|
|
29
|
+
html: opts.html,
|
|
30
|
+
inReplyTo: opts.inReplyTo,
|
|
31
|
+
references: opts.references,
|
|
32
|
+
});
|
|
33
|
+
return info;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=sender.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sender.js","sourceRoot":"","sources":["../../src/smtp/sender.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC,MAAM,gBAAgB,GAAsD;IAC1E,IAAI,EAAE,WAAW;IACjB,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI;IACtB,MAAM,EAAE,KAAK;IACb,GAAG,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE;CACnC,CAAC;AAEF,iCAAiC;AACjC,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU;IACxC,CAAC,CAAC;QACE,IAAI,EAAE;YACJ,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM;YAC9B,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ;YACjC,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU;SACnC;KACF;IACH,CAAC,CAAC,EAAE,CAAC;AAEP,MAAM,WAAW,GAAG,UAAU,CAAC,eAAe,CAAC;IAC7C,GAAG,gBAAgB;IACnB,GAAG,WAAW;CACR,CAAC,CAAC;AAYV,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAiB;IAC/C,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC;QACtC,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,UAAU,EAAE,IAAI,CAAC,UAAU;KAC5B,CAAC,CAAC;IACH,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smtp-server.d.ts","sourceRoot":"","sources":["../src/smtp-server.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createSmtpReceiver } from './smtp/receiver.js';
|
|
2
|
+
import { pool } from './db/index.js';
|
|
3
|
+
async function main() {
|
|
4
|
+
console.log('Starting AgentEmail SMTP...');
|
|
5
|
+
const smtp = createSmtpReceiver();
|
|
6
|
+
await smtp.start();
|
|
7
|
+
console.log('AgentEmail SMTP is running.');
|
|
8
|
+
const shutdown = async () => {
|
|
9
|
+
console.log('\nShutting down SMTP...');
|
|
10
|
+
await smtp.stop();
|
|
11
|
+
await pool.end();
|
|
12
|
+
process.exit(0);
|
|
13
|
+
};
|
|
14
|
+
process.on('SIGINT', shutdown);
|
|
15
|
+
process.on('SIGTERM', shutdown);
|
|
16
|
+
}
|
|
17
|
+
main().catch((err) => {
|
|
18
|
+
console.error('Fatal error:', err);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
});
|
|
21
|
+
//# sourceMappingURL=smtp-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smtp-server.js","sourceRoot":"","sources":["../src/smtp-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAErC,KAAK,UAAU,IAAI;IACjB,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAG,kBAAkB,EAAE,CAAC;IAClC,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;IACnB,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;IAE3C,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE;QAC1B,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;QACvC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;QACjB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;IACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/llms.txt
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# AgentMailbox
|
|
2
|
+
|
|
3
|
+
> Email addresses for AI agents. Zero cost per account.
|
|
4
|
+
|
|
5
|
+
AgentMailbox lets any AI agent have its own email address for receiving verification codes, order confirmations, and notifications. No per-account fees — creating an email is just a database row behind a catch-all domain.
|
|
6
|
+
|
|
7
|
+
## API Base URL
|
|
8
|
+
|
|
9
|
+
https://agent-email-api-production.up.railway.app
|
|
10
|
+
|
|
11
|
+
## Authentication
|
|
12
|
+
|
|
13
|
+
All endpoints except `POST /v1/orgs` require an API key via header:
|
|
14
|
+
- `Authorization: Bearer ae_xxxxx`
|
|
15
|
+
- or `X-API-Key: ae_xxxxx`
|
|
16
|
+
|
|
17
|
+
## API Endpoints
|
|
18
|
+
|
|
19
|
+
### Organizations
|
|
20
|
+
|
|
21
|
+
POST /v1/orgs
|
|
22
|
+
Body: { "name": "my-org" }
|
|
23
|
+
Returns: { org: { id, name, created_at }, api_key: "ae_..." }
|
|
24
|
+
Note: Save the api_key — it cannot be retrieved again.
|
|
25
|
+
|
|
26
|
+
### Accounts
|
|
27
|
+
|
|
28
|
+
POST /v1/accounts
|
|
29
|
+
Body: { "local_part": "grocery-bot", "display_name": "Grocery Bot" }
|
|
30
|
+
Returns: { account: { id, address: "grocery-bot@agentmailbox.io", display_name, created_at } }
|
|
31
|
+
|
|
32
|
+
GET /v1/accounts
|
|
33
|
+
Returns: { accounts: [{ id, address, display_name, created_at }] }
|
|
34
|
+
|
|
35
|
+
DELETE /v1/accounts/:address
|
|
36
|
+
Returns: { deleted: true }
|
|
37
|
+
|
|
38
|
+
### Messages
|
|
39
|
+
|
|
40
|
+
GET /v1/accounts/:address/messages
|
|
41
|
+
Query: ?limit=50&offset=0&since=ISO8601
|
|
42
|
+
Returns: { messages: [{ id, message_id, from, to, subject, text, html, direction, received_at, read_at }], count }
|
|
43
|
+
|
|
44
|
+
GET /v1/accounts/:address/messages/:messageId
|
|
45
|
+
Returns: { message: { ... } }
|
|
46
|
+
Side effect: marks message as read
|
|
47
|
+
|
|
48
|
+
GET /v1/accounts/:address/messages/wait
|
|
49
|
+
Query: ?timeout=30&since=ISO8601
|
|
50
|
+
Long-polls until a new message arrives or timeout. Returns 204 if no message.
|
|
51
|
+
Returns: { message: { ... } }
|
|
52
|
+
|
|
53
|
+
POST /v1/accounts/:address/messages/:messageId/reply
|
|
54
|
+
Body: { "text": "reply body", "html": "<p>optional html</p>" }
|
|
55
|
+
Returns: { sent: true, message_id }
|
|
56
|
+
|
|
57
|
+
POST /v1/accounts/:address/send
|
|
58
|
+
Body: { "to": "recipient@example.com", "subject": "Hello", "text": "body" }
|
|
59
|
+
Returns: { sent: true, message_id }
|
|
60
|
+
|
|
61
|
+
DELETE /v1/accounts/:address/messages/:messageId
|
|
62
|
+
Returns: { deleted: true }
|
|
63
|
+
|
|
64
|
+
### Health
|
|
65
|
+
|
|
66
|
+
GET /health
|
|
67
|
+
Returns: { status: "ok" }
|
|
68
|
+
|
|
69
|
+
## CLI Usage
|
|
70
|
+
|
|
71
|
+
npm install -g agentmailbox
|
|
72
|
+
|
|
73
|
+
export AGENT_EMAIL_API_URL=https://agent-email-api-production.up.railway.app
|
|
74
|
+
export AGENT_EMAIL_API_KEY=ae_xxxxx
|
|
75
|
+
|
|
76
|
+
agentmailbox org create my-org
|
|
77
|
+
agentmailbox account create grocery-bot
|
|
78
|
+
agentmailbox account list
|
|
79
|
+
agentmailbox account delete grocery-bot@agentmailbox.io
|
|
80
|
+
agentmailbox msg list grocery-bot@agentmailbox.io
|
|
81
|
+
agentmailbox msg read grocery-bot@agentmailbox.io <message-id>
|
|
82
|
+
agentmailbox msg wait grocery-bot@agentmailbox.io --timeout 60
|
|
83
|
+
agentmailbox msg reply grocery-bot@agentmailbox.io <message-id> "reply text"
|
|
84
|
+
agentmailbox msg send grocery-bot@agentmailbox.io recipient@example.com "Subject" "Body"
|
|
85
|
+
|
|
86
|
+
## Common Workflows
|
|
87
|
+
|
|
88
|
+
### Agent signup flow
|
|
89
|
+
1. Create account: POST /v1/accounts { "local_part": "bot-123" }
|
|
90
|
+
2. Use bot-123@agentmailbox.io to sign up for a service
|
|
91
|
+
3. Wait for verification email: GET /v1/accounts/bot-123@agentmailbox.io/messages/wait?timeout=60
|
|
92
|
+
4. Extract verification code from message text
|
|
93
|
+
5. Complete signup
|
|
94
|
+
|
|
95
|
+
### Reading order confirmations
|
|
96
|
+
1. GET /v1/accounts/bot-123@agentmailbox.io/messages?since=2024-01-01T00:00:00Z
|
|
97
|
+
2. Find message by subject pattern
|
|
98
|
+
3. GET /v1/accounts/bot-123@agentmailbox.io/messages/:id for full content
|
|
99
|
+
|
|
100
|
+
## Domain
|
|
101
|
+
|
|
102
|
+
All accounts use @agentmailbox.io
|
|
103
|
+
|
|
104
|
+
## Architecture
|
|
105
|
+
|
|
106
|
+
- API: Fastify on Railway
|
|
107
|
+
- SMTP: Node.js smtp-server on Fly.io (port 25)
|
|
108
|
+
- Database: PostgreSQL on Railway
|
|
109
|
+
- DNS: Vercel (MX, SPF, DMARC configured)
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agent-mailbox-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Email addresses for AI agents. Zero cost per account.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"email",
|
|
8
|
+
"agent",
|
|
9
|
+
"ai",
|
|
10
|
+
"smtp",
|
|
11
|
+
"mailbox",
|
|
12
|
+
"cli"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/agent-cards/agent-email.git"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"dev": "tsx watch src/index.ts",
|
|
21
|
+
"dev:api": "tsx watch src/api-server.ts",
|
|
22
|
+
"dev:smtp": "tsx watch src/smtp-server.ts",
|
|
23
|
+
"build": "tsc && cp src/db/schema.sql dist/db/schema.sql",
|
|
24
|
+
"start": "node dist/index.js",
|
|
25
|
+
"start:api": "node dist/api-server.js",
|
|
26
|
+
"start:smtp": "node dist/smtp-server.js",
|
|
27
|
+
"db:init": "tsx src/db/init.ts",
|
|
28
|
+
"cli": "tsx src/cli/index.ts"
|
|
29
|
+
},
|
|
30
|
+
"bin": {
|
|
31
|
+
"agentmailbox": "dist/cli/index.js"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@fastify/cors": "^10.0.0",
|
|
35
|
+
"commander": "^12.1.0",
|
|
36
|
+
"dotenv": "^16.4.0",
|
|
37
|
+
"fastify": "^5.2.0",
|
|
38
|
+
"mailparser": "^3.7.0",
|
|
39
|
+
"nodemailer": "^6.9.0",
|
|
40
|
+
"pg": "^8.13.0",
|
|
41
|
+
"smtp-server": "^3.13.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/mailparser": "^3.4.6",
|
|
45
|
+
"@types/node": "^22.0.0",
|
|
46
|
+
"@types/nodemailer": "^6.4.0",
|
|
47
|
+
"@types/pg": "^8.11.0",
|
|
48
|
+
"@types/smtp-server": "^3.5.0",
|
|
49
|
+
"tsx": "^4.19.0",
|
|
50
|
+
"typescript": "^5.7.0"
|
|
51
|
+
}
|
|
52
|
+
}
|