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.
Files changed (59) hide show
  1. package/dist/api/auth.d.ts +5 -0
  2. package/dist/api/auth.d.ts.map +1 -0
  3. package/dist/api/auth.js +25 -0
  4. package/dist/api/auth.js.map +1 -0
  5. package/dist/api/routes/accounts.d.ts +3 -0
  6. package/dist/api/routes/accounts.d.ts.map +1 -0
  7. package/dist/api/routes/accounts.js +49 -0
  8. package/dist/api/routes/accounts.js.map +1 -0
  9. package/dist/api/routes/messages.d.ts +3 -0
  10. package/dist/api/routes/messages.d.ts.map +1 -0
  11. package/dist/api/routes/messages.js +205 -0
  12. package/dist/api/routes/messages.js.map +1 -0
  13. package/dist/api/routes/orgs.d.ts +3 -0
  14. package/dist/api/routes/orgs.d.ts.map +1 -0
  15. package/dist/api/routes/orgs.js +20 -0
  16. package/dist/api/routes/orgs.js.map +1 -0
  17. package/dist/api/server.d.ts +5 -0
  18. package/dist/api/server.d.ts.map +1 -0
  19. package/dist/api/server.js +31 -0
  20. package/dist/api/server.js.map +1 -0
  21. package/dist/api-server.d.ts +2 -0
  22. package/dist/api-server.d.ts.map +1 -0
  23. package/dist/api-server.js +21 -0
  24. package/dist/api-server.js.map +1 -0
  25. package/dist/cli/index.d.ts +3 -0
  26. package/dist/cli/index.d.ts.map +1 -0
  27. package/dist/cli/index.js +150 -0
  28. package/dist/cli/index.js.map +1 -0
  29. package/dist/config.d.ts +20 -0
  30. package/dist/config.d.ts.map +1 -0
  31. package/dist/config.js +20 -0
  32. package/dist/config.js.map +1 -0
  33. package/dist/db/index.d.ts +3 -0
  34. package/dist/db/index.d.ts.map +1 -0
  35. package/dist/db/index.js +12 -0
  36. package/dist/db/index.js.map +1 -0
  37. package/dist/db/init.d.ts +2 -0
  38. package/dist/db/init.d.ts.map +1 -0
  39. package/dist/db/init.js +16 -0
  40. package/dist/db/init.js.map +1 -0
  41. package/dist/db/schema.sql +38 -0
  42. package/dist/index.d.ts +2 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +25 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/smtp/receiver.d.ts +5 -0
  47. package/dist/smtp/receiver.d.ts.map +1 -0
  48. package/dist/smtp/receiver.js +88 -0
  49. package/dist/smtp/receiver.js.map +1 -0
  50. package/dist/smtp/sender.d.ts +11 -0
  51. package/dist/smtp/sender.d.ts.map +1 -0
  52. package/dist/smtp/sender.js +35 -0
  53. package/dist/smtp/sender.js.map +1 -0
  54. package/dist/smtp-server.d.ts +2 -0
  55. package/dist/smtp-server.d.ts.map +1 -0
  56. package/dist/smtp-server.js +21 -0
  57. package/dist/smtp-server.js.map +1 -0
  58. package/llms.txt +109 -0
  59. 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"}
@@ -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,3 @@
1
+ import { FastifyInstance } from 'fastify';
2
+ export declare function accountRoutes(fastify: FastifyInstance): Promise<void>;
3
+ //# sourceMappingURL=accounts.d.ts.map
@@ -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,3 @@
1
+ import { FastifyInstance } from 'fastify';
2
+ export declare function messageRoutes(fastify: FastifyInstance): Promise<void>;
3
+ //# sourceMappingURL=messages.d.ts.map
@@ -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,3 @@
1
+ import { FastifyInstance } from 'fastify';
2
+ export declare function orgRoutes(fastify: FastifyInstance): Promise<void>;
3
+ //# sourceMappingURL=orgs.d.ts.map
@@ -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,5 @@
1
+ export declare function createApiServer(): Promise<{
2
+ start(): Promise<void>;
3
+ stop(): Promise<void>;
4
+ }>;
5
+ //# sourceMappingURL=server.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=api-server.d.ts.map
@@ -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,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,mDAAmD,CAAC;AACvG,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,EAAE,CAAC;AAEtD,KAAK,UAAU,GAAG,CAChB,MAAc,EACd,IAAY,EACZ,IAAU;IAEV,MAAM,OAAO,GAA2B;QACtC,cAAc,EAAE,kBAAkB;KACnC,CAAC;IACF,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,CAAC,eAAe,CAAC,GAAG,UAAU,OAAO,EAAE,CAAC;IACjD,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,GAAG,IAAI,EAAE,EAAE;QAC3C,MAAM;QACN,OAAO;QACP,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;KAC9C,CAAC,CAAC;IAEH,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IAEpC,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,OAAO,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,MAAM,GAAG,EAAG,IAAY,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;QACnE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,OAAO;KACJ,IAAI,CAAC,cAAc,CAAC;KACpB,WAAW,CAAC,4BAA4B,CAAC;KACzC,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,eAAe;AACf,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAEnC,GAAG;KACA,OAAO,CAAC,QAAQ,CAAC;KACjB,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAC;KACvC,MAAM,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE;IAC7B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;IACrC,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;IACzC,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3C,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IAC1C,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,mDAAmD,CAAC,CAAC;IACjE,OAAO,CAAC,GAAG,CAAC,yCAAyC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;AACvE,CAAC,CAAC,CAAC;AAEL,mBAAmB;AACnB,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAE3C,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,QAAQ,CAAC,cAAc,EAAE,6BAA6B,CAAC;KACvD,MAAM,CAAC,2BAA2B,EAAE,cAAc,CAAC;KACnD,MAAM,CAAC,KAAK,EAAE,SAAiB,EAAE,IAA8B,EAAE,EAAE;IAClE,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,MAAM,EAAE,cAAc,EAAE;QAC7C,UAAU,EAAE,SAAS;QACrB,YAAY,EAAE,IAAI,CAAC,WAAW;KAC/B,CAAC,CAAC;IACH,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;AAC1D,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;IAC9C,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAC5B,OAAO;IACT,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC9B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,YAAY,IAAI,SAAS,cAAc,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC;IAC3F,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;KAC3C,MAAM,CAAC,KAAK,EAAE,OAAe,EAAE,EAAE;IAChC,MAAM,GAAG,CAAC,QAAQ,EAAE,gBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC,WAAW,OAAO,EAAE,CAAC,CAAC;AACpC,CAAC,CAAC,CAAC;AAEL,mBAAmB;AACnB,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AAErD,GAAG;KACA,OAAO,CAAC,MAAM,CAAC;KACf,QAAQ,CAAC,WAAW,EAAE,eAAe,CAAC;KACtC,MAAM,CAAC,iBAAiB,EAAE,cAAc,EAAE,IAAI,CAAC;KAC/C,MAAM,CAAC,KAAK,EAAE,OAAe,EAAE,IAAuB,EAAE,EAAE;IACzD,MAAM,IAAI,GAAG,MAAM,GAAG,CACpB,KAAK,EACL,gBAAgB,kBAAkB,CAAC,OAAO,CAAC,mBAAmB,IAAI,CAAC,KAAK,EAAE,CAC3E,CAAC;IACF,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAC5B,OAAO;IACT,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;QACnC,MAAM,GAAG,GAAG,CAAC,CAAC,SAAS,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;QACvD,OAAO,CAAC,GAAG,CACT,GAAG,IAAI,IAAI,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,WAAW,EAAE,CAC7F,CAAC;IACJ,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,GAAG;KACA,OAAO,CAAC,MAAM,CAAC;KACf,QAAQ,CAAC,WAAW,EAAE,eAAe,CAAC;KACtC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;KAC9B,MAAM,CAAC,KAAK,EAAE,OAAe,EAAE,EAAU,EAAE,EAAE;IAC5C,MAAM,IAAI,GAAG,MAAM,GAAG,CACpB,KAAK,EACL,gBAAgB,kBAAkB,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAC7D,CAAC;IACF,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC;IACvB,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAClC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IACrC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IACzC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACnB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,cAAc,CAAC,CAAC;AAClD,CAAC,CAAC,CAAC;AAEL,GAAG;KACA,OAAO,CAAC,MAAM,CAAC;KACf,QAAQ,CAAC,WAAW,EAAE,eAAe,CAAC;KACtC,MAAM,CAAC,yBAAyB,EAAE,oBAAoB,EAAE,IAAI,CAAC;KAC7D,MAAM,CAAC,yBAAyB,EAAE,oCAAoC,CAAC;KACvE,MAAM,CACL,KAAK,EACH,OAAe,EACf,IAAyC,EACzC,EAAE;IACF,OAAO,CAAC,GAAG,CAAC,wBAAwB,OAAO,KAAK,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IAC9D,IAAI,IAAI,CAAC,KAAK;QAAE,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAEhD,MAAM,IAAI,GAAG,MAAM,GAAG,CACpB,KAAK,EACL,gBAAgB,kBAAkB,CAAC,OAAO,CAAC,kBAAkB,MAAM,EAAE,CACtE,CAAC;IACF,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;QAC1C,OAAO;IACT,CAAC;IACD,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC;IACvB,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC;IAC1D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACnB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,cAAc,CAAC,CAAC;AAClD,CAAC,CACF,CAAC;AAEJ,GAAG;KACA,OAAO,CAAC,OAAO,CAAC;KAChB,QAAQ,CAAC,WAAW,EAAE,eAAe,CAAC;KACtC,QAAQ,CAAC,MAAM,EAAE,wBAAwB,CAAC;KAC1C,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;KAChC,MAAM,CAAC,KAAK,EAAE,OAAe,EAAE,EAAU,EAAE,IAAY,EAAE,EAAE;IAC1D,MAAM,IAAI,GAAG,MAAM,GAAG,CACpB,MAAM,EACN,gBAAgB,kBAAkB,CAAC,OAAO,CAAC,aAAa,EAAE,QAAQ,EAClE,EAAE,IAAI,EAAE,IAAI,EAAE,CACf,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,2BAA2B,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;AAC5D,CAAC,CAAC,CAAC;AAEL,GAAG;KACA,OAAO,CAAC,MAAM,CAAC;KACf,QAAQ,CAAC,QAAQ,EAAE,6BAA6B,CAAC;KACjD,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;KACrC,QAAQ,CAAC,WAAW,EAAE,cAAc,CAAC;KACrC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;KAChC,MAAM,CAAC,KAAK,EAAE,IAAY,EAAE,EAAU,EAAE,OAAe,EAAE,IAAY,EAAE,EAAE;IACxE,MAAM,IAAI,GAAG,MAAM,GAAG,CACpB,MAAM,EACN,gBAAgB,kBAAkB,CAAC,IAAI,CAAC,OAAO,EAC/C,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAC5B,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;AACtD,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -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,3 @@
1
+ export declare const pool: import("pg").Pool;
2
+ export declare function query(text: string, params?: any[]): Promise<import("pg").QueryResult<any>>;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/db/init.ts"],"names":[],"mappings":""}
@@ -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);
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -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,5 @@
1
+ export declare function createSmtpReceiver(): {
2
+ start(): Promise<void>;
3
+ stop(): Promise<void>;
4
+ };
5
+ //# sourceMappingURL=receiver.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=smtp-server.d.ts.map
@@ -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
+ }