expxagents 0.11.2 → 0.12.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/dashboard/assets/{BufferResource-CR4DczHL.js → BufferResource-BcVsF5HP.js} +1 -1
- package/dist/dashboard/assets/{CanvasRenderer-DdWLm2t4.js → CanvasRenderer-kA1Maw0x.js} +1 -1
- package/dist/dashboard/assets/{JarvisView-yQv964kf.js → JarvisView-DBrCWArD.js} +1 -1
- package/dist/dashboard/assets/{RenderTargetSystem-DdTH8Un8.js → RenderTargetSystem-Bp9B4iP8.js} +1 -1
- package/dist/dashboard/assets/{WebGLRenderer-C6BYo_WV.js → WebGLRenderer-CNJyeb_W.js} +1 -1
- package/dist/dashboard/assets/{WebGPURenderer-C1UxCrJq.js → WebGPURenderer-DCbozzdc.js} +1 -1
- package/dist/dashboard/assets/{browserAll-BC0ycs7y.js → browserAll-Bu4cXbCn.js} +1 -1
- package/dist/dashboard/assets/index-zfHiMrG2.js +344 -0
- package/dist/dashboard/assets/{webworkerAll-D7SccyuO.js → webworkerAll-BFb9bpT-.js} +1 -1
- package/dist/dashboard/index.html +1 -1
- package/dist/data/opensquad.db +0 -0
- package/dist/server/app.d.ts.map +1 -1
- package/dist/server/app.js +30 -0
- package/dist/server/app.js.map +1 -1
- package/dist/server/config.d.ts +6 -0
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/config.js +14 -1
- package/dist/server/config.js.map +1 -1
- package/dist/server/db/__tests__/email-schema.test.d.ts +2 -0
- package/dist/server/db/__tests__/email-schema.test.d.ts.map +1 -0
- package/dist/server/db/__tests__/email-schema.test.js +53 -0
- package/dist/server/db/__tests__/email-schema.test.js.map +1 -0
- package/dist/server/db/schema.d.ts +1 -1
- package/dist/server/db/schema.d.ts.map +1 -1
- package/dist/server/db/schema.js +70 -0
- package/dist/server/db/schema.js.map +1 -1
- package/dist/server/email/__tests__/campaign-routes.test.d.ts +2 -0
- package/dist/server/email/__tests__/campaign-routes.test.d.ts.map +1 -0
- package/dist/server/email/__tests__/campaign-routes.test.js +216 -0
- package/dist/server/email/__tests__/campaign-routes.test.js.map +1 -0
- package/dist/server/email/__tests__/campaign-service.test.d.ts +2 -0
- package/dist/server/email/__tests__/campaign-service.test.d.ts.map +1 -0
- package/dist/server/email/__tests__/campaign-service.test.js +79 -0
- package/dist/server/email/__tests__/campaign-service.test.js.map +1 -0
- package/dist/server/email/__tests__/email-queue-worker.test.d.ts +2 -0
- package/dist/server/email/__tests__/email-queue-worker.test.d.ts.map +1 -0
- package/dist/server/email/__tests__/email-queue-worker.test.js +93 -0
- package/dist/server/email/__tests__/email-queue-worker.test.js.map +1 -0
- package/dist/server/email/__tests__/email-utils.test.d.ts +2 -0
- package/dist/server/email/__tests__/email-utils.test.d.ts.map +1 -0
- package/dist/server/email/__tests__/email-utils.test.js +36 -0
- package/dist/server/email/__tests__/email-utils.test.js.map +1 -0
- package/dist/server/email/__tests__/lead-routes.test.d.ts +2 -0
- package/dist/server/email/__tests__/lead-routes.test.d.ts.map +1 -0
- package/dist/server/email/__tests__/lead-routes.test.js +180 -0
- package/dist/server/email/__tests__/lead-routes.test.js.map +1 -0
- package/dist/server/email/__tests__/lead-service.test.d.ts +2 -0
- package/dist/server/email/__tests__/lead-service.test.d.ts.map +1 -0
- package/dist/server/email/__tests__/lead-service.test.js +113 -0
- package/dist/server/email/__tests__/lead-service.test.js.map +1 -0
- package/dist/server/email/__tests__/ses-client.test.d.ts +2 -0
- package/dist/server/email/__tests__/ses-client.test.d.ts.map +1 -0
- package/dist/server/email/__tests__/ses-client.test.js +48 -0
- package/dist/server/email/__tests__/ses-client.test.js.map +1 -0
- package/dist/server/email/__tests__/sns-webhook.test.d.ts +2 -0
- package/dist/server/email/__tests__/sns-webhook.test.d.ts.map +1 -0
- package/dist/server/email/__tests__/sns-webhook.test.js +40 -0
- package/dist/server/email/__tests__/sns-webhook.test.js.map +1 -0
- package/dist/server/email/campaign-routes.d.ts +8 -0
- package/dist/server/email/campaign-routes.d.ts.map +1 -0
- package/dist/server/email/campaign-routes.js +65 -0
- package/dist/server/email/campaign-routes.js.map +1 -0
- package/dist/server/email/campaign-service.d.ts +55 -0
- package/dist/server/email/campaign-service.d.ts.map +1 -0
- package/dist/server/email/campaign-service.js +89 -0
- package/dist/server/email/campaign-service.js.map +1 -0
- package/dist/server/email/email-queue-worker.d.ts +27 -0
- package/dist/server/email/email-queue-worker.d.ts.map +1 -0
- package/dist/server/email/email-queue-worker.js +119 -0
- package/dist/server/email/email-queue-worker.js.map +1 -0
- package/dist/server/email/email-utils.d.ts +5 -0
- package/dist/server/email/email-utils.d.ts.map +1 -0
- package/dist/server/email/email-utils.js +24 -0
- package/dist/server/email/email-utils.js.map +1 -0
- package/dist/server/email/lead-routes.d.ts +8 -0
- package/dist/server/email/lead-routes.d.ts.map +1 -0
- package/dist/server/email/lead-routes.js +56 -0
- package/dist/server/email/lead-routes.js.map +1 -0
- package/dist/server/email/lead-service.d.ts +66 -0
- package/dist/server/email/lead-service.d.ts.map +1 -0
- package/dist/server/email/lead-service.js +138 -0
- package/dist/server/email/lead-service.js.map +1 -0
- package/dist/server/email/ses-client.d.ts +27 -0
- package/dist/server/email/ses-client.d.ts.map +1 -0
- package/dist/server/email/ses-client.js +44 -0
- package/dist/server/email/ses-client.js.map +1 -0
- package/dist/server/email/sns-webhook.d.ts +10 -0
- package/dist/server/email/sns-webhook.d.ts.map +1 -0
- package/dist/server/email/sns-webhook.js +73 -0
- package/dist/server/email/sns-webhook.js.map +1 -0
- package/package.json +6 -2
- package/dist/dashboard/assets/index-3Noclaww.js +0 -344
- package/dist/data/opensquad.db-shm +0 -0
- package/dist/data/opensquad.db-wal +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { LeadService } from './lead-service.js';
|
|
2
|
+
export async function leadRoutes(app, opts) {
|
|
3
|
+
const service = new LeadService(opts.db);
|
|
4
|
+
app.post('/api/email/leads', { preHandler: [app.requireAuth] }, async (request, reply) => {
|
|
5
|
+
const lead = service.createLead(request.body);
|
|
6
|
+
return reply.send({ lead });
|
|
7
|
+
});
|
|
8
|
+
app.get('/api/email/leads', { preHandler: [app.requireAuth] }, async (request, reply) => {
|
|
9
|
+
const { tag, list, status, search, page, limit } = request.query;
|
|
10
|
+
const result = service.queryLeads({ tag, list, status, search, page: page ? parseInt(page) : undefined, limit: limit ? parseInt(limit) : undefined });
|
|
11
|
+
return reply.send(result);
|
|
12
|
+
});
|
|
13
|
+
app.post('/api/email/leads/batch', { preHandler: [app.requireAuth] }, async (request, reply) => {
|
|
14
|
+
const result = service.importBatch(request.body.leads, request.body.source_detail, request.body.source);
|
|
15
|
+
return reply.send(result);
|
|
16
|
+
});
|
|
17
|
+
app.patch('/api/email/leads/:id', { preHandler: [app.requireAuth] }, async (request, reply) => {
|
|
18
|
+
const lead = service.updateLead(request.params.id, request.body);
|
|
19
|
+
if (!lead)
|
|
20
|
+
return reply.status(404).send({ error: 'Lead not found' });
|
|
21
|
+
return reply.send({ lead });
|
|
22
|
+
});
|
|
23
|
+
app.delete('/api/email/leads/:id', { preHandler: [app.requireAuth] }, async (request, reply) => {
|
|
24
|
+
service.deleteLead(request.params.id);
|
|
25
|
+
return reply.send({ ok: true });
|
|
26
|
+
});
|
|
27
|
+
app.post('/api/email/leads/:id/tags', { preHandler: [app.requireAuth] }, async (request, reply) => {
|
|
28
|
+
service.addTags(request.params.id, request.body.tags);
|
|
29
|
+
const tags = service.getTagsForLead(request.params.id);
|
|
30
|
+
return reply.send({ tags });
|
|
31
|
+
});
|
|
32
|
+
app.delete('/api/email/leads/:id/tags', { preHandler: [app.requireAuth] }, async (request, reply) => {
|
|
33
|
+
service.removeTags(request.params.id, request.body.tags);
|
|
34
|
+
const tags = service.getTagsForLead(request.params.id);
|
|
35
|
+
return reply.send({ tags });
|
|
36
|
+
});
|
|
37
|
+
app.post('/api/email/leads/:id/lists', { preHandler: [app.requireAuth] }, async (request, reply) => {
|
|
38
|
+
for (const list of request.body.lists)
|
|
39
|
+
service.addToList(request.params.id, list);
|
|
40
|
+
const lists = service.getListsForLead(request.params.id);
|
|
41
|
+
return reply.send({ lists });
|
|
42
|
+
});
|
|
43
|
+
app.delete('/api/email/leads/:id/lists', { preHandler: [app.requireAuth] }, async (request, reply) => {
|
|
44
|
+
for (const list of request.body.lists)
|
|
45
|
+
service.removeFromList(request.params.id, list);
|
|
46
|
+
const lists = service.getListsForLead(request.params.id);
|
|
47
|
+
return reply.send({ lists });
|
|
48
|
+
});
|
|
49
|
+
app.get('/api/email/leads/lists', { preHandler: [app.requireAuth] }, async (_request, reply) => {
|
|
50
|
+
return reply.send({ lists: service.getListsWithCounts() });
|
|
51
|
+
});
|
|
52
|
+
app.get('/api/email/leads/tags', { preHandler: [app.requireAuth] }, async (_request, reply) => {
|
|
53
|
+
return reply.send({ tags: service.getTagsWithCounts() });
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=lead-routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lead-routes.js","sourceRoot":"","sources":["../../src/email/lead-routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
2
|
+
export interface CreateLeadData {
|
|
3
|
+
email: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
company?: string;
|
|
6
|
+
metadata?: Record<string, unknown>;
|
|
7
|
+
source?: string;
|
|
8
|
+
source_detail?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface LeadFilters {
|
|
11
|
+
tag?: string;
|
|
12
|
+
list?: string;
|
|
13
|
+
status?: string;
|
|
14
|
+
search?: string;
|
|
15
|
+
source?: string;
|
|
16
|
+
page?: number;
|
|
17
|
+
limit?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface Lead {
|
|
20
|
+
id: string;
|
|
21
|
+
email: string;
|
|
22
|
+
name: string | null;
|
|
23
|
+
company: string | null;
|
|
24
|
+
metadata: string | null;
|
|
25
|
+
status: string;
|
|
26
|
+
source: string | null;
|
|
27
|
+
source_detail: string | null;
|
|
28
|
+
created_at: string;
|
|
29
|
+
updated_at: string;
|
|
30
|
+
}
|
|
31
|
+
export declare class LeadService {
|
|
32
|
+
private db;
|
|
33
|
+
constructor(db: Database.Database);
|
|
34
|
+
createLead(data: CreateLeadData): Lead;
|
|
35
|
+
getLead(id: string): Lead | undefined;
|
|
36
|
+
updateLead(id: string, data: Partial<CreateLeadData>): Lead | undefined;
|
|
37
|
+
deleteLead(id: string): boolean;
|
|
38
|
+
addTags(leadId: string, tags: string[]): void;
|
|
39
|
+
removeTags(leadId: string, tags: string[]): void;
|
|
40
|
+
getTagsForLead(leadId: string): string[];
|
|
41
|
+
addToList(leadId: string, listName: string): void;
|
|
42
|
+
removeFromList(leadId: string, listName: string): void;
|
|
43
|
+
getListsForLead(leadId: string): string[];
|
|
44
|
+
queryLeads(filters: LeadFilters): {
|
|
45
|
+
data: Lead[];
|
|
46
|
+
total: number;
|
|
47
|
+
};
|
|
48
|
+
unsubscribe(email: string): boolean;
|
|
49
|
+
getListsWithCounts(): {
|
|
50
|
+
list_name: string;
|
|
51
|
+
count: number;
|
|
52
|
+
}[];
|
|
53
|
+
getTagsWithCounts(): {
|
|
54
|
+
tag: string;
|
|
55
|
+
count: number;
|
|
56
|
+
}[];
|
|
57
|
+
importBatch(leads: {
|
|
58
|
+
email: string;
|
|
59
|
+
name?: string;
|
|
60
|
+
company?: string;
|
|
61
|
+
metadata?: Record<string, unknown>;
|
|
62
|
+
}[], sourceDetail: string, source: string): {
|
|
63
|
+
imported: number;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=lead-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lead-service.d.ts","sourceRoot":"","sources":["../../src/email/lead-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
export class LeadService {
|
|
3
|
+
db;
|
|
4
|
+
constructor(db) {
|
|
5
|
+
this.db = db;
|
|
6
|
+
}
|
|
7
|
+
createLead(data) {
|
|
8
|
+
const id = crypto.randomUUID();
|
|
9
|
+
const meta = data.metadata ? JSON.stringify(data.metadata) : null;
|
|
10
|
+
const now = new Date().toISOString();
|
|
11
|
+
this.db.prepare(`
|
|
12
|
+
INSERT INTO leads (id, email, name, company, metadata, source, source_detail, created_at, updated_at)
|
|
13
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
14
|
+
ON CONFLICT(email) DO UPDATE SET
|
|
15
|
+
name = COALESCE(excluded.name, leads.name),
|
|
16
|
+
company = COALESCE(excluded.company, leads.company),
|
|
17
|
+
metadata = COALESCE(excluded.metadata, leads.metadata),
|
|
18
|
+
source = COALESCE(excluded.source, leads.source),
|
|
19
|
+
source_detail = COALESCE(excluded.source_detail, leads.source_detail),
|
|
20
|
+
updated_at = excluded.updated_at
|
|
21
|
+
`).run(id, data.email, data.name ?? null, data.company ?? null, meta, data.source ?? null, data.source_detail ?? null, now, now);
|
|
22
|
+
return this.db.prepare('SELECT * FROM leads WHERE email = ?').get(data.email);
|
|
23
|
+
}
|
|
24
|
+
getLead(id) {
|
|
25
|
+
return this.db.prepare('SELECT * FROM leads WHERE id = ?').get(id);
|
|
26
|
+
}
|
|
27
|
+
updateLead(id, data) {
|
|
28
|
+
const fields = [];
|
|
29
|
+
const values = [];
|
|
30
|
+
if (data.name !== undefined) {
|
|
31
|
+
fields.push('name = ?');
|
|
32
|
+
values.push(data.name);
|
|
33
|
+
}
|
|
34
|
+
if (data.company !== undefined) {
|
|
35
|
+
fields.push('company = ?');
|
|
36
|
+
values.push(data.company);
|
|
37
|
+
}
|
|
38
|
+
if (data.metadata !== undefined) {
|
|
39
|
+
fields.push('metadata = ?');
|
|
40
|
+
values.push(JSON.stringify(data.metadata));
|
|
41
|
+
}
|
|
42
|
+
if (fields.length === 0)
|
|
43
|
+
return this.getLead(id);
|
|
44
|
+
fields.push('updated_at = ?');
|
|
45
|
+
values.push(new Date().toISOString());
|
|
46
|
+
values.push(id);
|
|
47
|
+
this.db.prepare(`UPDATE leads SET ${fields.join(', ')} WHERE id = ?`).run(...values);
|
|
48
|
+
return this.getLead(id);
|
|
49
|
+
}
|
|
50
|
+
deleteLead(id) {
|
|
51
|
+
const result = this.db.prepare('DELETE FROM leads WHERE id = ?').run(id);
|
|
52
|
+
return result.changes > 0;
|
|
53
|
+
}
|
|
54
|
+
addTags(leadId, tags) {
|
|
55
|
+
const stmt = this.db.prepare('INSERT OR IGNORE INTO lead_tags (lead_id, tag) VALUES (?, ?)');
|
|
56
|
+
const tx = this.db.transaction((items) => {
|
|
57
|
+
for (const tag of items)
|
|
58
|
+
stmt.run(leadId, tag);
|
|
59
|
+
});
|
|
60
|
+
tx(tags);
|
|
61
|
+
}
|
|
62
|
+
removeTags(leadId, tags) {
|
|
63
|
+
const stmt = this.db.prepare('DELETE FROM lead_tags WHERE lead_id = ? AND tag = ?');
|
|
64
|
+
const tx = this.db.transaction((items) => {
|
|
65
|
+
for (const tag of items)
|
|
66
|
+
stmt.run(leadId, tag);
|
|
67
|
+
});
|
|
68
|
+
tx(tags);
|
|
69
|
+
}
|
|
70
|
+
getTagsForLead(leadId) {
|
|
71
|
+
return this.db.prepare('SELECT tag FROM lead_tags WHERE lead_id = ? ORDER BY tag').all(leadId).map(r => r.tag);
|
|
72
|
+
}
|
|
73
|
+
addToList(leadId, listName) {
|
|
74
|
+
this.db.prepare('INSERT OR IGNORE INTO lead_lists (lead_id, list_name) VALUES (?, ?)').run(leadId, listName);
|
|
75
|
+
}
|
|
76
|
+
removeFromList(leadId, listName) {
|
|
77
|
+
this.db.prepare('DELETE FROM lead_lists WHERE lead_id = ? AND list_name = ?').run(leadId, listName);
|
|
78
|
+
}
|
|
79
|
+
getListsForLead(leadId) {
|
|
80
|
+
return this.db.prepare('SELECT list_name FROM lead_lists WHERE lead_id = ? ORDER BY list_name').all(leadId).map(r => r.list_name);
|
|
81
|
+
}
|
|
82
|
+
queryLeads(filters) {
|
|
83
|
+
const page = filters.page ?? 1;
|
|
84
|
+
const limit = filters.limit ?? 50;
|
|
85
|
+
const offset = (page - 1) * limit;
|
|
86
|
+
const conditions = [];
|
|
87
|
+
const params = [];
|
|
88
|
+
if (filters.status) {
|
|
89
|
+
conditions.push('l.status = ?');
|
|
90
|
+
params.push(filters.status);
|
|
91
|
+
}
|
|
92
|
+
if (filters.source) {
|
|
93
|
+
conditions.push('l.source = ?');
|
|
94
|
+
params.push(filters.source);
|
|
95
|
+
}
|
|
96
|
+
if (filters.search) {
|
|
97
|
+
conditions.push('(l.email LIKE ? OR l.name LIKE ?)');
|
|
98
|
+
params.push(`%${filters.search}%`, `%${filters.search}%`);
|
|
99
|
+
}
|
|
100
|
+
if (filters.tag) {
|
|
101
|
+
conditions.push('EXISTS (SELECT 1 FROM lead_tags lt WHERE lt.lead_id = l.id AND lt.tag = ?)');
|
|
102
|
+
params.push(filters.tag);
|
|
103
|
+
}
|
|
104
|
+
if (filters.list) {
|
|
105
|
+
conditions.push('EXISTS (SELECT 1 FROM lead_lists ll WHERE ll.lead_id = l.id AND ll.list_name = ?)');
|
|
106
|
+
params.push(filters.list);
|
|
107
|
+
}
|
|
108
|
+
const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
109
|
+
const total = this.db.prepare(`SELECT COUNT(*) as count FROM leads l ${where}`).get(...params).count;
|
|
110
|
+
const data = this.db.prepare(`SELECT l.* FROM leads l ${where} ORDER BY l.created_at DESC LIMIT ? OFFSET ?`).all(...params, limit, offset);
|
|
111
|
+
return { data, total };
|
|
112
|
+
}
|
|
113
|
+
unsubscribe(email) {
|
|
114
|
+
const result = this.db.prepare("UPDATE leads SET status = 'unsubscribed', updated_at = ? WHERE email = ?").run(new Date().toISOString(), email);
|
|
115
|
+
if (result.changes > 0) {
|
|
116
|
+
this.db.prepare("DELETE FROM email_queue WHERE lead_id = (SELECT id FROM leads WHERE email = ?) AND status = 'pending'").run(email);
|
|
117
|
+
}
|
|
118
|
+
return result.changes > 0;
|
|
119
|
+
}
|
|
120
|
+
getListsWithCounts() {
|
|
121
|
+
return this.db.prepare('SELECT list_name, COUNT(*) as count FROM lead_lists GROUP BY list_name ORDER BY list_name').all();
|
|
122
|
+
}
|
|
123
|
+
getTagsWithCounts() {
|
|
124
|
+
return this.db.prepare('SELECT tag, COUNT(*) as count FROM lead_tags GROUP BY tag ORDER BY tag').all();
|
|
125
|
+
}
|
|
126
|
+
importBatch(leads, sourceDetail, source) {
|
|
127
|
+
let imported = 0;
|
|
128
|
+
const tx = this.db.transaction(() => {
|
|
129
|
+
for (const lead of leads) {
|
|
130
|
+
this.createLead({ ...lead, source, source_detail: sourceDetail });
|
|
131
|
+
imported++;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
tx();
|
|
135
|
+
return { imported };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=lead-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lead-service.js","sourceRoot":"","sources":["../../src/email/lead-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface SesClientConfig {
|
|
2
|
+
region: string;
|
|
3
|
+
accessKeyId?: string;
|
|
4
|
+
secretAccessKey?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface SendEmailParams {
|
|
7
|
+
to: string;
|
|
8
|
+
from: string;
|
|
9
|
+
fromName: string;
|
|
10
|
+
subject: string;
|
|
11
|
+
htmlBody: string;
|
|
12
|
+
textBody: string;
|
|
13
|
+
headers?: Record<string, string>;
|
|
14
|
+
}
|
|
15
|
+
export interface SesClient {
|
|
16
|
+
isConfigured(): boolean;
|
|
17
|
+
sendEmail(params: SendEmailParams): Promise<{
|
|
18
|
+
messageId: string;
|
|
19
|
+
}>;
|
|
20
|
+
getSendQuota(): Promise<{
|
|
21
|
+
maxSendRate: number;
|
|
22
|
+
max24HourSend: number;
|
|
23
|
+
sentLast24Hours: number;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
export declare function createSesClient(config: SesClientConfig): SesClient;
|
|
27
|
+
//# sourceMappingURL=ses-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ses-client.d.ts","sourceRoot":"","sources":["../../src/email/ses-client.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,SAAS;IACxB,YAAY,IAAI,OAAO,CAAC;IACxB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnE,YAAY,IAAI,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClG;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CA2ClE"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { SESv2Client, SendEmailCommand, GetAccountCommand } from '@aws-sdk/client-sesv2';
|
|
2
|
+
export function createSesClient(config) {
|
|
3
|
+
const configured = !!(config.accessKeyId && config.secretAccessKey);
|
|
4
|
+
const client = configured
|
|
5
|
+
? new SESv2Client({
|
|
6
|
+
region: config.region,
|
|
7
|
+
credentials: { accessKeyId: config.accessKeyId, secretAccessKey: config.secretAccessKey },
|
|
8
|
+
})
|
|
9
|
+
: null;
|
|
10
|
+
return {
|
|
11
|
+
isConfigured: () => configured,
|
|
12
|
+
async sendEmail(params) {
|
|
13
|
+
if (!client)
|
|
14
|
+
throw new Error('SES not configured');
|
|
15
|
+
const cmd = new SendEmailCommand({
|
|
16
|
+
FromEmailAddress: `${params.fromName} <${params.from}>`,
|
|
17
|
+
Destination: { ToAddresses: [params.to] },
|
|
18
|
+
Content: {
|
|
19
|
+
Simple: {
|
|
20
|
+
Subject: { Data: params.subject, Charset: 'UTF-8' },
|
|
21
|
+
Body: {
|
|
22
|
+
Html: { Data: params.htmlBody, Charset: 'UTF-8' },
|
|
23
|
+
Text: { Data: params.textBody, Charset: 'UTF-8' },
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
const res = await client.send(cmd);
|
|
29
|
+
return { messageId: res.MessageId ?? '' };
|
|
30
|
+
},
|
|
31
|
+
async getSendQuota() {
|
|
32
|
+
if (!client)
|
|
33
|
+
throw new Error('SES not configured');
|
|
34
|
+
const res = await client.send(new GetAccountCommand({}));
|
|
35
|
+
const q = res.SendQuota ?? {};
|
|
36
|
+
return {
|
|
37
|
+
maxSendRate: q.MaxSendRate ?? 1,
|
|
38
|
+
max24HourSend: q.Max24HourSend ?? 0,
|
|
39
|
+
sentLast24Hours: q.SentLast24Hours ?? 0,
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=ses-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ses-client.js","sourceRoot":"","sources":["../../src/email/ses-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAwBzF,MAAM,UAAU,eAAe,CAAC,MAAuB;IACrD,MAAM,UAAU,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,eAAe,CAAC,CAAC;IAEpE,MAAM,MAAM,GAAG,UAAU;QACvB,CAAC,CAAC,IAAI,WAAW,CAAC;YACd,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,WAAW,EAAE,EAAE,WAAW,EAAE,MAAM,CAAC,WAAY,EAAE,eAAe,EAAE,MAAM,CAAC,eAAgB,EAAE;SAC5F,CAAC;QACJ,CAAC,CAAC,IAAI,CAAC;IAET,OAAO;QACL,YAAY,EAAE,GAAG,EAAE,CAAC,UAAU;QAE9B,KAAK,CAAC,SAAS,CAAC,MAAuB;YACrC,IAAI,CAAC,MAAM;gBAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;YACnD,MAAM,GAAG,GAAG,IAAI,gBAAgB,CAAC;gBAC/B,gBAAgB,EAAE,GAAG,MAAM,CAAC,QAAQ,KAAK,MAAM,CAAC,IAAI,GAAG;gBACvD,WAAW,EAAE,EAAE,WAAW,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE;gBACzC,OAAO,EAAE;oBACP,MAAM,EAAE;wBACN,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE;wBACnD,IAAI,EAAE;4BACJ,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE;4BACjD,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE;yBAClD;qBACF;iBACF;aACF,CAAC,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACnC,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC;QAC5C,CAAC;QAED,KAAK,CAAC,YAAY;YAChB,IAAI,CAAC,MAAM;gBAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;YACnD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,iBAAiB,CAAC,EAAE,CAAC,CAAC,CAAC;YACzD,MAAM,CAAC,GAAG,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC;YAC9B,OAAO;gBACL,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,CAAC;gBAC/B,aAAa,EAAE,CAAC,CAAC,aAAa,IAAI,CAAC;gBACnC,eAAe,EAAE,CAAC,CAAC,eAAe,IAAI,CAAC;aACxC,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
|
2
|
+
import type Database from 'better-sqlite3';
|
|
3
|
+
export declare function processSnsNotification(db: Database.Database, notification: any): void;
|
|
4
|
+
interface SnsWebhookOptions extends FastifyPluginOptions {
|
|
5
|
+
db: Database.Database;
|
|
6
|
+
jwtSecret: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function snsWebhookRoutes(app: FastifyInstance, opts: SnsWebhookOptions): Promise<void>;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=sns-webhook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sns-webhook.d.ts","sourceRoot":"","sources":["../../src/email/sns-webhook.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AACrE,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAG3C,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,GAAG,IAAI,CA6BrF;AAED,UAAU,iBAAkB,SAAQ,oBAAoB;IACtD,EAAE,EAAE,QAAQ,CAAC,QAAQ,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsCnG"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import jwt from 'jsonwebtoken';
|
|
2
|
+
export function processSnsNotification(db, notification) {
|
|
3
|
+
const messageId = notification.mail?.messageId;
|
|
4
|
+
if (!messageId)
|
|
5
|
+
return;
|
|
6
|
+
const queueItem = db.prepare('SELECT id, campaign_id, lead_id, status FROM email_queue WHERE ses_message_id = ?').get(messageId);
|
|
7
|
+
if (!queueItem)
|
|
8
|
+
return;
|
|
9
|
+
const type = notification.notificationType;
|
|
10
|
+
if (type === 'Bounce') {
|
|
11
|
+
if (queueItem.status === 'bounced')
|
|
12
|
+
return; // idempotent
|
|
13
|
+
db.transaction(() => {
|
|
14
|
+
db.prepare("UPDATE email_queue SET status = 'bounced' WHERE id = ?").run(queueItem.id);
|
|
15
|
+
db.prepare('UPDATE email_campaigns SET bounce_count = bounce_count + 1 WHERE id = ?').run(queueItem.campaign_id);
|
|
16
|
+
if (queueItem.lead_id) {
|
|
17
|
+
db.prepare("UPDATE leads SET status = 'bounced', updated_at = ? WHERE id = ?").run(new Date().toISOString(), queueItem.lead_id);
|
|
18
|
+
}
|
|
19
|
+
})();
|
|
20
|
+
}
|
|
21
|
+
else if (type === 'Complaint') {
|
|
22
|
+
if (queueItem.status === 'complained')
|
|
23
|
+
return;
|
|
24
|
+
db.transaction(() => {
|
|
25
|
+
db.prepare("UPDATE email_queue SET status = 'complained' WHERE id = ?").run(queueItem.id);
|
|
26
|
+
db.prepare('UPDATE email_campaigns SET complaint_count = complaint_count + 1 WHERE id = ?').run(queueItem.campaign_id);
|
|
27
|
+
if (queueItem.lead_id) {
|
|
28
|
+
db.prepare("UPDATE leads SET status = 'complained', updated_at = ? WHERE id = ?").run(new Date().toISOString(), queueItem.lead_id);
|
|
29
|
+
}
|
|
30
|
+
})();
|
|
31
|
+
}
|
|
32
|
+
// Delivery notifications are ignored (optional metric)
|
|
33
|
+
}
|
|
34
|
+
export async function snsWebhookRoutes(app, opts) {
|
|
35
|
+
const { db, jwtSecret } = opts;
|
|
36
|
+
// SNS webhook — public, no auth
|
|
37
|
+
app.post('/api/email/sns', async (request, reply) => {
|
|
38
|
+
const body = request.body;
|
|
39
|
+
// Handle subscription confirmation
|
|
40
|
+
if (body.Type === 'SubscriptionConfirmation' && body.SubscribeURL) {
|
|
41
|
+
try {
|
|
42
|
+
await fetch(body.SubscribeURL);
|
|
43
|
+
}
|
|
44
|
+
catch { /* ignore */ }
|
|
45
|
+
return reply.send({ ok: true });
|
|
46
|
+
}
|
|
47
|
+
// Process notification
|
|
48
|
+
if (body.Type === 'Notification') {
|
|
49
|
+
try {
|
|
50
|
+
const message = typeof body.Message === 'string' ? JSON.parse(body.Message) : body.Message;
|
|
51
|
+
processSnsNotification(db, message);
|
|
52
|
+
}
|
|
53
|
+
catch { /* ignore malformed */ }
|
|
54
|
+
}
|
|
55
|
+
return reply.send({ ok: true });
|
|
56
|
+
});
|
|
57
|
+
// Unsubscribe — public, token-based
|
|
58
|
+
app.get('/api/email/unsubscribe/:token', async (request, reply) => {
|
|
59
|
+
try {
|
|
60
|
+
const decoded = jwt.verify(request.params.token, jwtSecret);
|
|
61
|
+
if (decoded.action !== 'unsubscribe' || !decoded.email) {
|
|
62
|
+
return reply.status(400).send({ error: 'Invalid token' });
|
|
63
|
+
}
|
|
64
|
+
db.prepare("UPDATE leads SET status = 'unsubscribed', updated_at = ? WHERE email = ?").run(new Date().toISOString(), decoded.email);
|
|
65
|
+
db.prepare("DELETE FROM email_queue WHERE lead_id = (SELECT id FROM leads WHERE email = ?) AND status = 'pending'").run(decoded.email);
|
|
66
|
+
return reply.type('text/html').send('<html><body><h2>You have been unsubscribed.</h2><p>You will no longer receive emails from us.</p></body></html>');
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return reply.status(400).send({ error: 'Invalid or expired token' });
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=sns-webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sns-webhook.js","sourceRoot":"","sources":["../../src/email/sns-webhook.ts"],"names":[],"mappings":"AAEA,OAAO,GAAG,MAAM,cAAc,CAAC;AAE/B,MAAM,UAAU,sBAAsB,CAAC,EAAqB,EAAE,YAAiB;IAC7E,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,EAAE,SAAS,CAAC;IAC/C,IAAI,CAAC,SAAS;QAAE,OAAO;IAEvB,MAAM,SAAS,GAAG,EAAE,CAAC,OAAO,CAAC,mFAAmF,CAAC,CAAC,GAAG,CAAC,SAAS,CAAQ,CAAC;IACxI,IAAI,CAAC,SAAS;QAAE,OAAO;IAEvB,MAAM,IAAI,GAAG,YAAY,CAAC,gBAAgB,CAAC;IAE3C,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,IAAI,SAAS,CAAC,MAAM,KAAK,SAAS;YAAE,OAAO,CAAC,aAAa;QACzD,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;YAClB,EAAE,CAAC,OAAO,CAAC,wDAAwD,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YACvF,EAAE,CAAC,OAAO,CAAC,yEAAyE,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;YACjH,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;gBACtB,EAAE,CAAC,OAAO,CAAC,kEAAkE,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;YAClI,CAAC;QACH,CAAC,CAAC,EAAE,CAAC;IACP,CAAC;SAAM,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;QAChC,IAAI,SAAS,CAAC,MAAM,KAAK,YAAY;YAAE,OAAO;QAC9C,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;YAClB,EAAE,CAAC,OAAO,CAAC,2DAA2D,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YAC1F,EAAE,CAAC,OAAO,CAAC,+EAA+E,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;YACvH,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;gBACtB,EAAE,CAAC,OAAO,CAAC,qEAAqE,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;YACrI,CAAC;QACH,CAAC,CAAC,EAAE,CAAC;IACP,CAAC;IACD,uDAAuD;AACzD,CAAC;AAOD,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,GAAoB,EAAE,IAAuB;IAClF,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAE/B,gCAAgC;IAChC,GAAG,CAAC,IAAI,CAAC,gBAAgB,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QAClD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAW,CAAC;QAEjC,mCAAmC;QACnC,IAAI,IAAI,CAAC,IAAI,KAAK,0BAA0B,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YAClE,IAAI,CAAC;gBAAC,MAAM,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;YAC9D,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QAClC,CAAC;QAED,uBAAuB;QACvB,IAAI,IAAI,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;YACjC,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;gBAC3F,sBAAsB,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;YACtC,CAAC;YAAC,MAAM,CAAC,CAAC,sBAAsB,CAAC,CAAC;QACpC,CAAC;QAED,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,oCAAoC;IACpC,GAAG,CAAC,GAAG,CAAgC,+BAA+B,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QAC/F,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,SAAS,CAAQ,CAAC;YACnE,IAAI,OAAO,CAAC,MAAM,KAAK,aAAa,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;gBACvD,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC;YAC5D,CAAC;YACD,EAAE,CAAC,OAAO,CAAC,0EAA0E,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;YACpI,EAAE,CAAC,OAAO,CAAC,uGAAuG,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACvI,OAAO,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,iHAAiH,CAAC,CAAC;QACzJ,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC,CAAC;QACvE,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expxagents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Multi-agent orchestration platform for AI squads",
|
|
5
5
|
"author": "ExpxAgents",
|
|
6
6
|
"license": "MIT",
|
|
@@ -49,7 +49,11 @@
|
|
|
49
49
|
"dotenv": "^16.4.7",
|
|
50
50
|
"fastify": "^5.2.1",
|
|
51
51
|
"jsonwebtoken": "^9.0.2",
|
|
52
|
-
"yaml": "^2.7.1"
|
|
52
|
+
"yaml": "^2.7.1",
|
|
53
|
+
"@aws-sdk/client-sesv2": "^3.800.0",
|
|
54
|
+
"@fastify/multipart": "^9.0.3",
|
|
55
|
+
"csv-parse": "^5.6.0",
|
|
56
|
+
"node-cron": "^4.0.7"
|
|
53
57
|
},
|
|
54
58
|
"devDependencies": {
|
|
55
59
|
"@types/js-yaml": "^4.0.9",
|