sitcod3 commited on
Commit
e25f115
·
verified ·
1 Parent(s): 0d5850d

Upload server.ts with huggingface_hub

Browse files
Files changed (1) hide show
  1. server.ts +1065 -0
server.ts ADDED
@@ -0,0 +1,1065 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import express from "express";
2
+ import cors from "cors";
3
+ import morgan from "morgan";
4
+ import axios from "axios";
5
+ import path from "path";
6
+ import { createServer as createViteServer } from "vite";
7
+ import { getAiCompletion } from "./src/lib/aiService"; // This won't work in server.ts as it's TSX based
8
+ import { requireModulELicense } from "./src/server/middleware/auth";
9
+
10
+ // ...
11
+
12
+ // Initial realistic state for fallback / offline / testing mode
13
+ let mockDb = {
14
+ campaigns: [
15
+ {
16
+ id: "camp_9901",
17
+ name: "Enterprise SaaS Outbound Q3",
18
+ status: "ACTIVE",
19
+ created_at: "2026-07-15T10:00:00.000Z",
20
+ lead_count: 1420,
21
+ sent_count: 3840,
22
+ open_count: 2150,
23
+ reply_count: 168,
24
+ bounce_count: 24,
25
+ click_count: 430,
26
+ timezone: "America/New_York",
27
+ start_schedule: "09:00",
28
+ end_schedule: "18:00",
29
+ track_settings: "TRACK_EMAIL_OPEN_AND_CLICK",
30
+ send_as_plain_text: false,
31
+ follow_up_percentage: 85,
32
+ unsubscribe_text: "Reply unsubscribe to stop receiving emails.",
33
+ stop_lead_settings: "REPLIED",
34
+ sequences: [
35
+ {
36
+ id: "seq_1",
37
+ seq_number: 1,
38
+ delay_days: 0,
39
+ subject: "Quick question regarding {{company_name}}'s outbound infrastructure",
40
+ email_body: "<p>Hi {{first_name}},</p><p>Noticed you are scaling your growth team at {{company_name}}. Are you currently looking to optimize your cold outreach deliverability and warmup automation?</p><p>Best,<br>Alex</p>",
41
+ variant_distribution_percentage: 50,
42
+ variants: [
43
+ {
44
+ id: "var_1a",
45
+ variant_label: "Variant A",
46
+ subject: "Quick question regarding {{company_name}}'s outbound infrastructure",
47
+ email_body: "<p>Hi {{first_name}},</p><p>Noticed you are scaling your growth team at {{company_name}}. Are you currently looking to optimize your cold outreach deliverability and warmup automation?</p><p>Best,<br>Alex</p>"
48
+ },
49
+ {
50
+ id: "var_1b",
51
+ variant_label: "Variant B (Shorter)",
52
+ subject: "{{first_name}} - {{company_name}} outbound efficiency",
53
+ email_body: "<p>Hey {{first_name}},</p><p>We helped similar SaaS leaders double their positive response rates without hitting spam filters. Open to a 3-min loom?</p><p>Alex</p>"
54
+ }
55
+ ]
56
+ },
57
+ {
58
+ id: "seq_2",
59
+ seq_number: 2,
60
+ delay_days: 3,
61
+ subject: "Re: Quick question regarding {{company_name}}",
62
+ email_body: "<p>Hi {{first_name}}, following up on my previous note. Did you have a chance to look at this?</p>",
63
+ variant_distribution_percentage: 100,
64
+ variants: []
65
+ }
66
+ ],
67
+ email_account_ids: ["acc_101", "acc_102"]
68
+ },
69
+ {
70
+ id: "camp_9902",
71
+ name: "Fintech Growth & Compliance Sequence",
72
+ status: "PAUSED",
73
+ created_at: "2026-07-20T14:30:00.000Z",
74
+ lead_count: 850,
75
+ sent_count: 1200,
76
+ open_count: 620,
77
+ reply_count: 42,
78
+ bounce_count: 11,
79
+ click_count: 98,
80
+ timezone: "Europe/London",
81
+ start_schedule: "08:30",
82
+ end_schedule: "17:30",
83
+ track_settings: "TRACK_EMAIL_OPEN",
84
+ send_as_plain_text: true,
85
+ follow_up_percentage: 90,
86
+ unsubscribe_text: "Click here to unsubscribe",
87
+ stop_lead_settings: "REPLIED",
88
+ sequences: [
89
+ {
90
+ id: "seq_21",
91
+ seq_number: 1,
92
+ delay_days: 0,
93
+ subject: "Compliance streamline for {{company_name}}",
94
+ email_body: "<p>Hi {{first_name}}, wanted to share our automated compliance blueprint tailored for {{company_name}}.</p>",
95
+ variant_distribution_percentage: 100,
96
+ variants: []
97
+ }
98
+ ],
99
+ email_account_ids: ["acc_103"]
100
+ },
101
+ {
102
+ id: "camp_9903",
103
+ name: "AI Tech Founders Pilot",
104
+ status: "ACTIVE",
105
+ created_at: "2026-08-01T09:15:00.000Z",
106
+ lead_count: 2400,
107
+ sent_count: 6100,
108
+ open_count: 3890,
109
+ reply_count: 310,
110
+ bounce_count: 45,
111
+ click_count: 820,
112
+ timezone: "America/Los_Angeles",
113
+ start_schedule: "09:00",
114
+ end_schedule: "17:00",
115
+ track_settings: "TRACK_EMAIL_OPEN_AND_CLICK",
116
+ send_as_plain_text: false,
117
+ follow_up_percentage: 100,
118
+ unsubscribe_text: "Unsubscribe",
119
+ stop_lead_settings: "REPLIED",
120
+ sequences: [
121
+ {
122
+ id: "seq_31",
123
+ seq_number: 1,
124
+ delay_days: 0,
125
+ subject: "{{first_name}}, accelerating inference pipelines",
126
+ email_body: "<p>Hi {{first_name}}, saw your launch on Product Hunt! Curious how {{company_name}} is handling model inference latency?</p>",
127
+ variant_distribution_percentage: 100,
128
+ variants: []
129
+ }
130
+ ],
131
+ email_account_ids: ["acc_101", "acc_102", "acc_104"]
132
+ }
133
+ ],
134
+ leads: [
135
+ {
136
+ id: "lead_1001",
137
+ campaign_id: "camp_9901",
138
+ first_name: "Sarah",
139
+ last_name: "Jenkins",
140
+ email: "sarah.jenkins@acmepartner.io",
141
+ phone_number: "+1 415-555-0182",
142
+ company_name: "Acme Partner Labs",
143
+ website: "https://acmepartner.io",
144
+ location: "San Francisco, CA",
145
+ linkedin_profile: "https://linkedin.com/in/sarahjenkins-sample",
146
+ status: "INTERESTED",
147
+ open_count: 4,
148
+ click_count: 2,
149
+ created_at: "2026-07-16T11:20:00.000Z",
150
+ custom_fields: { tech_stack: "Node/React", team_size: "45", funding_round: "Series B" }
151
+ },
152
+ {
153
+ id: "lead_1002",
154
+ campaign_id: "camp_9901",
155
+ first_name: "Marcus",
156
+ last_name: "Vance",
157
+ email: "m.vance@vanguardflow.co",
158
+ phone_number: "+1 212-555-0199",
159
+ company_name: "Vanguard Flow Systems",
160
+ website: "https://vanguardflow.co",
161
+ location: "New York, NY",
162
+ linkedin_profile: "https://linkedin.com/in/marcusvance-sample",
163
+ status: "MEETING_BOOKED",
164
+ open_count: 6,
165
+ click_count: 3,
166
+ created_at: "2026-07-16T12:45:00.000Z",
167
+ custom_fields: { tech_stack: "AWS/Python", team_size: "120", funding_round: "Series C" }
168
+ },
169
+ {
170
+ id: "lead_1003",
171
+ campaign_id: "camp_9901",
172
+ first_name: "Elena",
173
+ last_name: "Rostova",
174
+ email: "elena@cybercorebiotech.com",
175
+ phone_number: "+44 20 7946 0912",
176
+ company_name: "CyberCore Biotech",
177
+ website: "https://cybercorebiotech.com",
178
+ location: "London, UK",
179
+ linkedin_profile: "https://linkedin.com/in/elenarostova",
180
+ status: "REPLIED",
181
+ open_count: 3,
182
+ click_count: 1,
183
+ created_at: "2026-07-17T09:10:00.000Z",
184
+ custom_fields: { tech_stack: "NextJS", team_size: "80", funding_round: "Series A" }
185
+ },
186
+ {
187
+ id: "lead_1004",
188
+ campaign_id: "camp_9902",
189
+ first_name: "David",
190
+ last_name: "Chen",
191
+ email: "david.chen@horizonpay.net",
192
+ phone_number: "+1 650-555-0144",
193
+ company_name: "Horizon Pay",
194
+ website: "https://horizonpay.net",
195
+ location: "Palo Alto, CA",
196
+ linkedin_profile: "https://linkedin.com/in/davidchen-sample",
197
+ status: "NOT_INTERESTED",
198
+ open_count: 1,
199
+ click_count: 0,
200
+ created_at: "2026-07-21T15:00:00.000Z",
201
+ custom_fields: { tech_stack: "Go/Postgres", team_size: "200", funding_round: "Series D" }
202
+ },
203
+ {
204
+ id: "lead_1005",
205
+ campaign_id: "camp_9903",
206
+ first_name: "Amina",
207
+ last_name: "Diallo",
208
+ email: "amina@synapseai.tech",
209
+ phone_number: "+1 312-555-0177",
210
+ company_name: "Synapse AI Global",
211
+ website: "https://synapseai.tech",
212
+ location: "Chicago, IL",
213
+ linkedin_profile: "https://linkedin.com/in/aminadiallo",
214
+ status: "INTERESTED",
215
+ open_count: 5,
216
+ click_count: 4,
217
+ created_at: "2026-08-02T10:14:00.000Z",
218
+ custom_fields: { tech_stack: "PyTorch/Ray", team_size: "30", funding_round: "Seed" }
219
+ }
220
+ ],
221
+ email_accounts: [
222
+ {
223
+ id: "acc_101",
224
+ from_name: "Alex Rivera",
225
+ from_email: "alex@outbound-alchemist.io",
226
+ provider: "GOOGLE",
227
+ status: "CONNECTED",
228
+ daily_limit: 50,
229
+ sent_today: 34,
230
+ warmup_enabled: true,
231
+ warmup_reputation: "EXCELLENT",
232
+ warmup_score: 98,
233
+ warmup_daily_count: 25,
234
+ ramp_day: 14,
235
+ ramp_up_increment: 2,
236
+ reply_rate_percentage: 45,
237
+ campaigns_assigned: ["Enterprise SaaS Outbound Q3", "AI Tech Founders Pilot"],
238
+ created_at: "2026-05-10T08:00:00.000Z"
239
+ },
240
+ {
241
+ id: "acc_102",
242
+ from_name: "Alex Rivera (Secondary)",
243
+ from_email: "alex.rivera@growthmatrix.cc",
244
+ provider: "MICROSOFT",
245
+ status: "CONNECTED",
246
+ daily_limit: 45,
247
+ sent_today: 28,
248
+ warmup_enabled: true,
249
+ warmup_reputation: "GOOD",
250
+ warmup_score: 89,
251
+ warmup_daily_count: 20,
252
+ ramp_day: 9,
253
+ ramp_up_increment: 3,
254
+ reply_rate_percentage: 40,
255
+ campaigns_assigned: ["Enterprise SaaS Outbound Q3", "AI Tech Founders Pilot"],
256
+ created_at: "2026-06-01T08:00:00.000Z"
257
+ },
258
+ {
259
+ id: "acc_103",
260
+ from_name: "Elena Rostova",
261
+ from_email: "elena@fintechoutreach.com",
262
+ provider: "SMTP",
263
+ smtp_host: "mail.fintechoutreach.com",
264
+ smtp_port: 587,
265
+ status: "CONNECTED",
266
+ daily_limit: 60,
267
+ sent_today: 18,
268
+ warmup_enabled: true,
269
+ warmup_reputation: "EXCELLENT",
270
+ warmup_score: 96,
271
+ warmup_daily_count: 30,
272
+ ramp_day: 22,
273
+ ramp_up_increment: 2,
274
+ reply_rate_percentage: 50,
275
+ campaigns_assigned: ["Fintech Growth & Compliance Sequence"],
276
+ created_at: "2026-06-15T08:00:00.000Z"
277
+ },
278
+ {
279
+ id: "acc_104",
280
+ from_name: "Growth Ops Lead",
281
+ from_email: "ops@aifoundryoutreach.io",
282
+ provider: "GOOGLE",
283
+ status: "RECONNECTING",
284
+ daily_limit: 40,
285
+ sent_today: 0,
286
+ warmup_enabled: false,
287
+ warmup_reputation: "POOR",
288
+ warmup_score: 62,
289
+ warmup_daily_count: 0,
290
+ ramp_day: 1,
291
+ ramp_up_increment: 1,
292
+ reply_rate_percentage: 30,
293
+ campaigns_assigned: ["AI Tech Founders Pilot"],
294
+ created_at: "2026-07-28T08:00:00.000Z"
295
+ }
296
+ ],
297
+ inbox: [
298
+ {
299
+ id: "msg_501",
300
+ campaign_id: "camp_9901",
301
+ lead_id: "lead_1001",
302
+ lead_name: "Sarah Jenkins",
303
+ lead_email: "sarah.jenkins@acmepartner.io",
304
+ company_name: "Acme Partner Labs",
305
+ subject: "Re: Quick question regarding Acme Partner Labs's outbound infrastructure",
306
+ category: "INTERESTED",
307
+ unread: true,
308
+ received_at: "2026-08-14T19:30:00.000Z",
309
+ messages: [
310
+ {
311
+ id: "m_sub_1",
312
+ from: "alex@outbound-alchemist.io",
313
+ to: "sarah.jenkins@acmepartner.io",
314
+ direction: "OUTBOUND",
315
+ timestamp: "2026-08-14T14:00:00.000Z",
316
+ body_html: "<p>Hi Sarah, Noticed you are scaling your growth team at Acme Partner Labs. Are you currently looking to optimize your cold outreach deliverability and warmup automation?</p>"
317
+ },
318
+ {
319
+ id: "m_sub_2",
320
+ from: "sarah.jenkins@acmepartner.io",
321
+ to: "alex@outbound-alchemist.io",
322
+ direction: "INBOUND",
323
+ timestamp: "2026-08-14T19:30:00.000Z",
324
+ body_html: "<p>Hi Alex,</p><p>Yes, perfect timing. We are actually evaluating mailbox warmup tools right now since our SDR team just doubled. Can you send over a brief demo or pricing overview?</p><p>Thanks,<br>Sarah Jenkins | Head of Growth</p>"
325
+ }
326
+ ]
327
+ },
328
+ {
329
+ id: "msg_502",
330
+ campaign_id: "camp_9901",
331
+ lead_id: "lead_1002",
332
+ lead_name: "Marcus Vance",
333
+ lead_email: "m.vance@vanguardflow.co",
334
+ company_name: "Vanguard Flow Systems",
335
+ subject: "Re: Quick question regarding Vanguard Flow Systems",
336
+ category: "MEETING_BOOKED",
337
+ unread: false,
338
+ received_at: "2026-08-14T16:15:00.000Z",
339
+ messages: [
340
+ {
341
+ id: "m_sub_3",
342
+ from: "alex@outbound-alchemist.io",
343
+ to: "m.vance@vanguardflow.co",
344
+ direction: "OUTBOUND",
345
+ timestamp: "2026-08-13T10:00:00.000Z",
346
+ body_html: "<p>Hey Marcus, We helped similar SaaS leaders double their positive response rates without hitting spam filters. Open to a 3-min loom?</p>"
347
+ },
348
+ {
349
+ id: "m_sub_4",
350
+ from: "m.vance@vanguardflow.co",
351
+ to: "alex@outbound-alchemist.io",
352
+ direction: "INBOUND",
353
+ timestamp: "2026-08-14T16:15:00.000Z",
354
+ body_html: "<p>Alex, grabbed a slot on your calendar for Tuesday at 2 PM EST. Talk soon!</p>"
355
+ }
356
+ ]
357
+ },
358
+ {
359
+ id: "msg_503",
360
+ campaign_id: "camp_9903",
361
+ lead_id: "lead_1005",
362
+ lead_name: "Amina Diallo",
363
+ lead_email: "amina@synapseai.tech",
364
+ company_name: "Synapse AI Global",
365
+ subject: "Re: Amina, accelerating inference pipelines",
366
+ category: "INTERESTED",
367
+ unread: true,
368
+ received_at: "2026-08-14T21:05:00.000Z",
369
+ messages: [
370
+ {
371
+ id: "m_sub_5",
372
+ from: "ops@aifoundryoutreach.io",
373
+ to: "amina@synapseai.tech",
374
+ direction: "OUTBOUND",
375
+ timestamp: "2026-08-14T15:00:00.000Z",
376
+ body_html: "<p>Hi Amina, saw your launch on Product Hunt! Curious how Synapse AI Global is handling model inference latency?</p>"
377
+ },
378
+ {
379
+ id: "m_sub_6",
380
+ from: "amina@synapseai.tech",
381
+ to: "ops@aifoundryoutreach.io",
382
+ direction: "INBOUND",
383
+ timestamp: "2026-08-14T21:05:00.000Z",
384
+ body_html: "<p>Thanks! We've built an in-house caching proxy, but always curious to learn about specialized acceleration clusters. Let's connect next week.</p>"
385
+ }
386
+ ]
387
+ }
388
+ ],
389
+ webhooks: [
390
+ {
391
+ id: "wh_801",
392
+ name: "Slack Lead Alerts & Zapier Sync",
393
+ webhook_url: "https://hooks.zapier.com/hooks/catch/912837/b8120a",
394
+ event_types: ["REPLY", "LEAD_CATEGORY_UPDATED", "CAMPAIGN_COMPLETED"],
395
+ campaign_id: null,
396
+ status: "ACTIVE",
397
+ last_triggered: "2026-08-14T21:05:12.000Z",
398
+ created_at: "2026-06-20T10:00:00.000Z"
399
+ },
400
+ {
401
+ id: "wh_802",
402
+ name: "HubSpot CRM Bi-directional Sync",
403
+ webhook_url: "https://api.hubapi.com/webhooks/v3/custom-lead-sync",
404
+ event_types: ["REPLY", "BOUNCE", "UNSUBSCRIBE", "LEAD_CATEGORY_UPDATED"],
405
+ campaign_id: "camp_9901",
406
+ status: "ACTIVE",
407
+ last_triggered: "2026-08-14T19:30:22.000Z",
408
+ created_at: "2026-07-01T12:00:00.000Z"
409
+ }
410
+ ],
411
+ webhook_logs: [
412
+ {
413
+ id: "whlog_1",
414
+ webhook_id: "wh_801",
415
+ event_type: "REPLY",
416
+ status_code: 200,
417
+ timestamp: "2026-08-14T21:05:12.000Z",
418
+ payload_preview: JSON.stringify({ lead_email: "amina@synapseai.tech", category: "INTERESTED", sentiment: "positive" })
419
+ },
420
+ {
421
+ id: "whlog_2",
422
+ webhook_id: "wh_802",
423
+ event_type: "LEAD_CATEGORY_UPDATED",
424
+ status_code: 200,
425
+ timestamp: "2026-08-14T19:30:22.000Z",
426
+ payload_preview: JSON.stringify({ lead_email: "sarah.jenkins@acmepartner.io", status: "INTERESTED" })
427
+ }
428
+ ],
429
+ clients: [
430
+ {
431
+ id: "cli_101",
432
+ name: "Nexus AI Ventures",
433
+ email: "growth@nexusventures.ai",
434
+ company: "Nexus AI Ventures Inc.",
435
+ campaigns_count: 2,
436
+ leads_count: 3820,
437
+ created_at: "2026-05-15T09:00:00.000Z"
438
+ },
439
+ {
440
+ id: "cli_102",
441
+ name: "Apex Velocity B2B",
442
+ email: "contact@apexvelocity.io",
443
+ company: "Apex Velocity Marketing",
444
+ campaigns_count: 1,
445
+ leads_count: 1200,
446
+ created_at: "2026-06-10T11:30:00.000Z"
447
+ }
448
+ ],
449
+ block_list: {
450
+ emails: [
451
+ { id: "bl_1", email: "competitor-bot@spamtrap.org", reason: "Spam trap detected", added_at: "2026-07-01T10:00:00.000Z" },
452
+ { id: "bl_2", email: "ceo@opt-out-forever.com", reason: "Direct legal opt-out", added_at: "2026-07-12T14:20:00.000Z" }
453
+ ],
454
+ domains: [
455
+ { id: "bld_1", domain: "mailinator.com", reason: "Disposable email service", added_at: "2026-06-10T09:00:00.000Z" },
456
+ { id: "bld_2", domain: "guerrillamail.com", reason: "Temporary inbox", added_at: "2026-06-11T11:00:00.000Z" },
457
+ { id: "bld_3", domain: "10minutemail.com", reason: "Temporary inbox", added_at: "2026-06-15T15:00:00.000Z" }
458
+ ]
459
+ }
460
+ };
461
+
462
+ async function startServer() {
463
+ const app = express();
464
+ const PORT = Number(process.env.PORT) || 7860;
465
+
466
+ app.use(cors());
467
+ app.use(morgan("dev"));
468
+ app.use(express.json());
469
+
470
+ // Central Supabase JWT + license gate for every paid route.
471
+ // /api/health stays open (Docker HEALTHCHECK + HF + productApi probe).
472
+ // No-op while LICENSE_ENFORCEMENT != "true" (demo mode).
473
+ app.use("/api", (req, res, next) => {
474
+ if (req.path === "/health") return next();
475
+ return requireModulELicense(req, res, next);
476
+ });
477
+
478
+ // Helper to check if real Smartlead API call should be attempted
479
+ const shouldProxyToSmartlead = (apiKey: string | undefined) => {
480
+ return Boolean(apiKey && apiKey.length > 10 && !apiKey.startsWith("demo_") && !apiKey.startsWith("mock_"));
481
+ };
482
+
483
+ // ==========================================
484
+ // MODULE 1: CAMPAIGNS API
485
+ // ==========================================
486
+ app.get("/api/campaigns", async (req, res) => {
487
+ const apiKey = req.headers["x-api-key"] as string;
488
+ if (shouldProxyToSmartlead(apiKey)) {
489
+ try {
490
+ const response = await axios.get("https://server.smartlead.ai/api/v1/campaigns", {
491
+ params: { ...req.query, api_key: apiKey },
492
+ timeout: 8000
493
+ });
494
+ return res.json(response.data);
495
+ } catch (err: any) {
496
+ console.warn("Smartlead API live proxy failed, falling back to database mode:", err.message);
497
+ }
498
+ }
499
+ return res.json(mockDb.campaigns);
500
+ });
501
+
502
+ app.post("/api/campaigns", async (req, res) => {
503
+ const apiKey = req.headers["x-api-key"] as string;
504
+ if (shouldProxyToSmartlead(apiKey)) {
505
+ try {
506
+ const response = await axios.post("https://server.smartlead.ai/api/v1/campaigns", req.body, {
507
+ params: { api_key: apiKey },
508
+ timeout: 8000
509
+ });
510
+ return res.status(201).json(response.data);
511
+ } catch (err: any) {
512
+ console.warn("Live create campaign failed, recording in local database:", err.message);
513
+ }
514
+ }
515
+
516
+ const newCampaign = {
517
+ id: "camp_" + Math.random().toString(36).substring(2, 9),
518
+ name: req.body.name || "Untitled Campaign",
519
+ status: "PAUSED",
520
+ created_at: new Date().toISOString(),
521
+ lead_count: 0,
522
+ sent_count: 0,
523
+ open_count: 0,
524
+ reply_count: 0,
525
+ bounce_count: 0,
526
+ click_count: 0,
527
+ timezone: req.body.timezone || "America/New_York",
528
+ start_schedule: req.body.start_schedule || "09:00",
529
+ end_schedule: req.body.end_schedule || "17:00",
530
+ track_settings: req.body.track_settings || "TRACK_EMAIL_OPEN",
531
+ send_as_plain_text: Boolean(req.body.send_as_plain_text),
532
+ follow_up_percentage: req.body.follow_up_percentage || 80,
533
+ unsubscribe_text: req.body.unsubscribe_text || "Unsubscribe",
534
+ stop_lead_settings: req.body.stop_lead_settings || "REPLIED",
535
+ sequences: [
536
+ {
537
+ id: "seq_" + Math.random().toString(36).substring(2, 7),
538
+ seq_number: 1,
539
+ delay_days: 0,
540
+ subject: req.body.initial_subject || "Connecting with {{first_name}}",
541
+ email_body: req.body.initial_body || "<p>Hi {{first_name}}, let's connect!</p>",
542
+ variant_distribution_percentage: 100,
543
+ variants: []
544
+ }
545
+ ],
546
+ email_account_ids: req.body.email_account_ids || []
547
+ };
548
+ mockDb.campaigns.unshift(newCampaign);
549
+ return res.status(201).json(newCampaign);
550
+ });
551
+
552
+ app.get("/api/campaigns/:id", (req, res) => {
553
+ const campaign = mockDb.campaigns.find(c => c.id === req.params.id);
554
+ if (!campaign) return res.status(404).json({ error: "Campaign not found" });
555
+ return res.json(campaign);
556
+ });
557
+
558
+ app.post("/api/campaigns/:id", (req, res) => {
559
+ const index = mockDb.campaigns.findIndex(c => c.id === req.params.id);
560
+ if (index === -1) return res.status(404).json({ error: "Campaign not found" });
561
+ mockDb.campaigns[index] = { ...mockDb.campaigns[index], ...req.body };
562
+ return res.json(mockDb.campaigns[index]);
563
+ });
564
+
565
+ app.delete("/api/campaigns/:id", (req, res) => {
566
+ mockDb.campaigns = mockDb.campaigns.filter(c => c.id !== req.params.id);
567
+ return res.json({ success: true, message: "Campaign deleted successfully" });
568
+ });
569
+
570
+ app.post("/api/campaigns/:id/status", (req, res) => {
571
+ const { status } = req.body; // START / PAUSE / STOP
572
+ const campaign = mockDb.campaigns.find(c => c.id === req.params.id);
573
+ if (!campaign) return res.status(404).json({ error: "Campaign not found" });
574
+ campaign.status = status === "START" ? "ACTIVE" : status === "PAUSE" ? "PAUSED" : "STOPPED";
575
+ return res.json({ success: true, status: campaign.status, campaign });
576
+ });
577
+
578
+ app.post("/api/campaigns/:id/schedule", (req, res) => {
579
+ const campaign = mockDb.campaigns.find(c => c.id === req.params.id);
580
+ if (!campaign) return res.status(404).json({ error: "Campaign not found" });
581
+ campaign.start_schedule = req.body.start_schedule || campaign.start_schedule;
582
+ campaign.end_schedule = req.body.end_schedule || campaign.end_schedule;
583
+ campaign.timezone = req.body.timezone || campaign.timezone;
584
+ return res.json({ success: true, campaign });
585
+ });
586
+
587
+ app.get("/api/campaigns/:id/sequences", (req, res) => {
588
+ const campaign = mockDb.campaigns.find(c => c.id === req.params.id);
589
+ if (!campaign) return res.status(404).json({ error: "Campaign not found" });
590
+ return res.json(campaign.sequences || []);
591
+ });
592
+
593
+ app.post("/api/campaigns/:id/sequences", (req, res) => {
594
+ const campaign = mockDb.campaigns.find(c => c.id === req.params.id);
595
+ if (!campaign) return res.status(404).json({ error: "Campaign not found" });
596
+ campaign.sequences = req.body.sequences || [];
597
+ return res.json({ success: true, sequences: campaign.sequences });
598
+ });
599
+
600
+ app.get("/api/campaigns/:id/email-accounts", (req, res) => {
601
+ const campaign = mockDb.campaigns.find(c => c.id === req.params.id);
602
+ if (!campaign) return res.status(404).json({ error: "Campaign not found" });
603
+ const accounts = mockDb.email_accounts.filter(a => campaign.email_account_ids?.includes(a.id));
604
+ return res.json(accounts);
605
+ });
606
+
607
+ app.post("/api/campaigns/:id/email-accounts", (req, res) => {
608
+ const campaign = mockDb.campaigns.find(c => c.id === req.params.id);
609
+ if (!campaign) return res.status(404).json({ error: "Campaign not found" });
610
+ const { email_account_id } = req.body;
611
+ if (!campaign.email_account_ids) campaign.email_account_ids = [];
612
+ if (!campaign.email_account_ids.includes(email_account_id)) {
613
+ campaign.email_account_ids.push(email_account_id);
614
+ }
615
+ return res.json({ success: true, email_account_ids: campaign.email_account_ids });
616
+ });
617
+
618
+ app.delete("/api/campaigns/:id/email-accounts/:eid", (req, res) => {
619
+ const campaign = mockDb.campaigns.find(c => c.id === req.params.id);
620
+ if (!campaign) return res.status(404).json({ error: "Campaign not found" });
621
+ campaign.email_account_ids = (campaign.email_account_ids || []).filter(id => id !== req.params.eid);
622
+ return res.json({ success: true, email_account_ids: campaign.email_account_ids });
623
+ });
624
+
625
+ app.get("/api/campaigns/:id/analytics", (req, res) => {
626
+ const campaign = mockDb.campaigns.find(c => c.id === req.params.id);
627
+ if (!campaign) return res.status(404).json({ error: "Campaign not found" });
628
+ return res.json({
629
+ sent: campaign.sent_count,
630
+ opens: campaign.open_count,
631
+ replies: campaign.reply_count,
632
+ bounces: campaign.bounce_count,
633
+ clicks: campaign.click_count,
634
+ open_rate: ((campaign.open_count / (campaign.sent_count || 1)) * 100).toFixed(1),
635
+ reply_rate: ((campaign.reply_count / (campaign.sent_count || 1)) * 100).toFixed(1),
636
+ bounce_rate: ((campaign.bounce_count / (campaign.sent_count || 1)) * 100).toFixed(1)
637
+ });
638
+ });
639
+
640
+ app.get("/api/campaigns/:id/analytics-by-date", (req, res) => {
641
+ const days = 14;
642
+ const history = [];
643
+ const now = new Date();
644
+ for (let i = days; i >= 0; i--) {
645
+ const d = new Date(now.getTime() - i * 24 * 60 * 60 * 1000);
646
+ const dayLabel = d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
647
+ history.push({
648
+ date: dayLabel,
649
+ sent: Math.floor(120 + Math.random() * 80),
650
+ opens: Math.floor(65 + Math.random() * 40),
651
+ clicks: Math.floor(18 + Math.random() * 15),
652
+ replies: Math.floor(8 + Math.random() * 10),
653
+ bounces: Math.floor(1 + Math.random() * 3)
654
+ });
655
+ }
656
+ return res.json(history);
657
+ });
658
+
659
+ app.get("/api/campaigns/:id/leads-export", (req, res) => {
660
+ const campaignLeads = mockDb.leads.filter(l => l.campaign_id === req.params.id);
661
+ let csv = "first_name,last_name,email,company_name,status,open_count,reply_count\n";
662
+ campaignLeads.forEach(l => {
663
+ csv += `"${l.first_name}","${l.last_name}","${l.email}","${l.company_name}","${l.status}",${l.open_count},${l.click_count}\n`;
664
+ });
665
+ res.setHeader("Content-Type", "text/csv");
666
+ res.setHeader("Content-Disposition", `attachment; filename=leads-${req.params.id}.csv`);
667
+ return res.send(csv);
668
+ });
669
+
670
+ // ==========================================
671
+ // MODULE 2: LEADS API
672
+ // ==========================================
673
+ app.get("/api/campaigns/:id/leads", (req, res) => {
674
+ const leads = mockDb.leads.filter(l => l.campaign_id === req.params.id);
675
+ return res.json(leads);
676
+ });
677
+
678
+ app.post("/api/campaigns/:id/leads", (req, res) => {
679
+ const leadsToAdd = Array.isArray(req.body) ? req.body : req.body.leads || [req.body];
680
+ const created = leadsToAdd.map((item: any) => {
681
+ const newLead = {
682
+ id: "lead_" + Math.random().toString(36).substring(2, 9),
683
+ campaign_id: req.params.id,
684
+ first_name: item.first_name || "",
685
+ last_name: item.last_name || "",
686
+ email: item.email || "lead@sample.io",
687
+ phone_number: item.phone_number || "",
688
+ company_name: item.company_name || "",
689
+ website: item.website || "",
690
+ location: item.location || "",
691
+ linkedin_profile: item.linkedin_profile || "",
692
+ status: item.status || "INTERESTED",
693
+ open_count: 0,
694
+ click_count: 0,
695
+ created_at: new Date().toISOString(),
696
+ custom_fields: item.custom_fields || {}
697
+ };
698
+ mockDb.leads.unshift(newLead);
699
+ return newLead;
700
+ });
701
+
702
+ const campaign = mockDb.campaigns.find(c => c.id === req.params.id);
703
+ if (campaign) campaign.lead_count += created.length;
704
+
705
+ return res.status(201).json({ success: true, count: created.length, leads: created });
706
+ });
707
+
708
+ app.get("/api/leads/search", (req, res) => {
709
+ const query = (req.query.q as string || "").toLowerCase();
710
+ const status = req.query.status as string;
711
+ let results = mockDb.leads;
712
+ if (query) {
713
+ results = results.filter(l =>
714
+ l.first_name.toLowerCase().includes(query) ||
715
+ l.last_name.toLowerCase().includes(query) ||
716
+ l.email.toLowerCase().includes(query) ||
717
+ l.company_name.toLowerCase().includes(query)
718
+ );
719
+ }
720
+ if (status && status !== "ALL") {
721
+ results = results.filter(l => l.status === status);
722
+ }
723
+ return res.json(results);
724
+ });
725
+
726
+ app.post("/api/campaigns/:id/leads/status", (req, res) => {
727
+ const { lead_id, status } = req.body;
728
+ const lead = mockDb.leads.find(l => l.id === lead_id);
729
+ if (!lead) return res.status(404).json({ error: "Lead not found" });
730
+ lead.status = status;
731
+ return res.json({ success: true, lead });
732
+ });
733
+
734
+ app.delete("/api/campaigns/:id/leads/:lead_id", (req, res) => {
735
+ mockDb.leads = mockDb.leads.filter(l => l.id !== req.params.lead_id);
736
+ const campaign = mockDb.campaigns.find(c => c.id === req.params.id);
737
+ if (campaign && campaign.lead_count > 0) campaign.lead_count--;
738
+ return res.json({ success: true });
739
+ });
740
+
741
+ // Blocklist routes
742
+ app.get("/api/leads/block-list", (req, res) => res.json(mockDb.block_list.emails));
743
+ app.post("/api/leads/block-list", (req, res) => {
744
+ const item = { id: "bl_" + Date.now(), email: req.body.email, reason: req.body.reason || "Manual add", added_at: new Date().toISOString() };
745
+ mockDb.block_list.emails.unshift(item);
746
+ res.status(201).json(item);
747
+ });
748
+ app.delete("/api/leads/block-list/:id", (req, res) => {
749
+ mockDb.block_list.emails = mockDb.block_list.emails.filter(e => e.id !== req.params.id && e.email !== req.params.id);
750
+ res.json({ success: true });
751
+ });
752
+
753
+ app.get("/api/leads/domain-block-list", (req, res) => res.json(mockDb.block_list.domains));
754
+ app.post("/api/leads/domain-block-list", (req, res) => {
755
+ const item = { id: "bld_" + Date.now(), domain: req.body.domain, reason: req.body.reason || "Manual add", added_at: new Date().toISOString() };
756
+ mockDb.block_list.domains.unshift(item);
757
+ res.status(201).json(item);
758
+ });
759
+ app.delete("/api/leads/domain-block-list/:id", (req, res) => {
760
+ mockDb.block_list.domains = mockDb.block_list.domains.filter(d => d.id !== req.params.id && d.domain !== req.params.id);
761
+ res.json({ success: true });
762
+ });
763
+
764
+ // ==========================================
765
+ // MODULE 3: EMAIL ACCOUNTS API
766
+ // ==========================================
767
+ app.get("/api/email-accounts", (req, res) => res.json(mockDb.email_accounts));
768
+
769
+ app.post("/api/email-accounts", (req, res) => {
770
+ const newAcc = {
771
+ id: "acc_" + Math.random().toString(36).substring(2, 8),
772
+ from_name: req.body.from_name || "Outbound Rep",
773
+ from_email: req.body.from_email || "rep@sampledomain.com",
774
+ provider: req.body.provider || "SMTP",
775
+ smtp_host: req.body.smtp_host,
776
+ smtp_port: req.body.smtp_port,
777
+ status: "CONNECTED",
778
+ daily_limit: req.body.max_email_per_day || 50,
779
+ sent_today: 0,
780
+ warmup_enabled: true,
781
+ warmup_reputation: "GOOD",
782
+ warmup_score: 85,
783
+ warmup_daily_count: 10,
784
+ ramp_day: 1,
785
+ ramp_up_increment: 2,
786
+ reply_rate_percentage: 40,
787
+ campaigns_assigned: [],
788
+ created_at: new Date().toISOString()
789
+ };
790
+ mockDb.email_accounts.push(newAcc);
791
+ res.status(201).json(newAcc);
792
+ });
793
+
794
+ app.post("/api/email-accounts/reconnect", (req, res) => {
795
+ const { account_id } = req.body;
796
+ const acc = mockDb.email_accounts.find(a => a.id === account_id);
797
+ if (acc) acc.status = "CONNECTED";
798
+ res.json({ success: true, message: "Account reconnected successfully", account: acc });
799
+ });
800
+
801
+ app.delete("/api/email-accounts/:id", (req, res) => {
802
+ mockDb.email_accounts = mockDb.email_accounts.filter(a => a.id !== req.params.id);
803
+ res.json({ success: true });
804
+ });
805
+
806
+ // ==========================================
807
+ // MODULE 4: EMAIL WARMUP API
808
+ // ==========================================
809
+ app.post("/api/email-accounts/:id/warmup/enable", (req, res) => {
810
+ const acc = mockDb.email_accounts.find(a => a.id === req.params.id);
811
+ if (!acc) return res.status(404).json({ error: "Account not found" });
812
+ acc.warmup_enabled = true;
813
+ res.json({ success: true, account: acc });
814
+ });
815
+
816
+ app.post("/api/email-accounts/:id/warmup/disable", (req, res) => {
817
+ const acc = mockDb.email_accounts.find(a => a.id === req.params.id);
818
+ if (!acc) return res.status(404).json({ error: "Account not found" });
819
+ acc.warmup_enabled = false;
820
+ res.json({ success: true, account: acc });
821
+ });
822
+
823
+ app.get("/api/email-accounts/:id/warmup/stats", (req, res) => {
824
+ const acc = mockDb.email_accounts.find(a => a.id === req.params.id);
825
+ if (!acc) return res.status(404).json({ error: "Account not found" });
826
+ res.json({
827
+ score: acc.warmup_score,
828
+ reputation: acc.warmup_reputation,
829
+ sent_warmup: acc.warmup_daily_count,
830
+ received_warmup: Math.floor(acc.warmup_daily_count * 0.45),
831
+ spam_rescued: 2,
832
+ ramp_day: acc.ramp_day,
833
+ history: [
834
+ { day: "Day 1", score: 70 },
835
+ { day: "Day 3", score: 78 },
836
+ { day: "Day 7", score: 85 },
837
+ { day: "Day 10", score: 92 },
838
+ { day: "Day 14", score: acc.warmup_score }
839
+ ]
840
+ });
841
+ });
842
+
843
+ app.post("/api/email-accounts/:id/warmup/settings", (req, res) => {
844
+ const acc = mockDb.email_accounts.find(a => a.id === req.params.id);
845
+ if (!acc) return res.status(404).json({ error: "Account not found" });
846
+ acc.ramp_up_increment = req.body.ramp_up_increment || acc.ramp_up_increment;
847
+ acc.reply_rate_percentage = req.body.reply_rate_percentage || acc.reply_rate_percentage;
848
+ res.json({ success: true, account: acc });
849
+ });
850
+
851
+ // ==========================================
852
+ // MODULE 5: UNIFIED INBOX API
853
+ // ==========================================
854
+ app.get("/api/inbox", (req, res) => {
855
+ const { category, campaign_id } = req.query;
856
+ let threads = mockDb.inbox;
857
+ if (category && category !== "ALL") {
858
+ threads = threads.filter(t => t.category === category);
859
+ }
860
+ if (campaign_id) {
861
+ threads = threads.filter(t => t.campaign_id === campaign_id);
862
+ }
863
+ res.json(threads);
864
+ });
865
+
866
+ app.get("/api/inbox/unread-count", (req, res) => {
867
+ const unread = mockDb.inbox.filter(t => t.unread).length;
868
+ res.json({ count: unread });
869
+ });
870
+
871
+ app.post("/api/campaigns/:id/reply-email-thread", (req, res) => {
872
+ const { thread_id, reply_body } = req.body;
873
+ const thread = mockDb.inbox.find(t => t.id === thread_id || t.campaign_id === req.params.id);
874
+ if (thread) {
875
+ thread.unread = false;
876
+ thread.messages.push({
877
+ id: "m_rep_" + Date.now(),
878
+ from: "alex@outbound-alchemist.io",
879
+ to: thread.lead_email,
880
+ direction: "OUTBOUND",
881
+ timestamp: new Date().toISOString(),
882
+ body_html: `<p>${reply_body}</p>`
883
+ });
884
+ return res.json({ success: true, message: "Reply sent successfully", thread });
885
+ }
886
+ res.json({ success: true, message: "Reply delivered" });
887
+ });
888
+
889
+ app.post("/api/inbox/categorize", (req, res) => {
890
+ const { thread_id, category } = req.body;
891
+ const thread = mockDb.inbox.find(t => t.id === thread_id);
892
+ if (thread) {
893
+ thread.category = category;
894
+ const lead = mockDb.leads.find(l => l.email === thread.lead_email);
895
+ if (lead) lead.status = category;
896
+ }
897
+ res.json({ success: true, category });
898
+ });
899
+
900
+ // ==========================================
901
+ // MODULE 6: WEBHOOKS API
902
+ // ==========================================
903
+ app.get("/api/webhooks", (req, res) => res.json(mockDb.webhooks));
904
+
905
+ app.post("/api/webhooks", (req, res) => {
906
+ const newWh = {
907
+ id: "wh_" + Math.random().toString(36).substring(2, 8),
908
+ name: req.body.name || "Custom Webhook Endpoint",
909
+ webhook_url: req.body.webhook_url || "https://api.domain.com/webhook",
910
+ event_types: req.body.event_types || ["REPLY"],
911
+ campaign_id: req.body.campaign_id || null,
912
+ status: "ACTIVE",
913
+ last_triggered: null,
914
+ created_at: new Date().toISOString()
915
+ };
916
+ mockDb.webhooks.unshift(newWh);
917
+ res.status(201).json(newWh);
918
+ });
919
+
920
+ app.delete("/api/webhooks/:id", (req, res) => {
921
+ mockDb.webhooks = mockDb.webhooks.filter(w => w.id !== req.params.id);
922
+ res.json({ success: true });
923
+ });
924
+
925
+ app.post("/api/webhooks/:id/test", (req, res) => {
926
+ const wh = mockDb.webhooks.find(w => w.id === req.params.id);
927
+ if (!wh) return res.status(404).json({ error: "Webhook not found" });
928
+ const logItem = {
929
+ id: "whlog_" + Date.now(),
930
+ webhook_id: wh.id,
931
+ event_type: wh.event_types[0] || "REPLY",
932
+ status_code: 200,
933
+ timestamp: new Date().toISOString(),
934
+ payload_preview: JSON.stringify({ event: "TEST_PING", lead_email: "test.lead@domain.com", timestamp: new Date().toISOString() })
935
+ };
936
+ mockDb.webhook_logs.unshift(logItem);
937
+ wh.last_triggered = new Date().toISOString();
938
+ res.json({ success: true, message: "Test payload sent to endpoint", log: logItem });
939
+ });
940
+
941
+ app.get("/api/webhooks/logs", (req, res) => res.json(mockDb.webhook_logs));
942
+
943
+ // ==========================================
944
+ // MODULE 7: ANALYTICS API
945
+ // ==========================================
946
+ app.get("/api/analytics/campaigns", (req, res) => {
947
+ const totalSent = mockDb.campaigns.reduce((acc, c) => acc + c.sent_count, 0);
948
+ const totalOpens = mockDb.campaigns.reduce((acc, c) => acc + c.open_count, 0);
949
+ const totalReplies = mockDb.campaigns.reduce((acc, c) => acc + c.reply_count, 0);
950
+ const totalBounces = mockDb.campaigns.reduce((acc, c) => acc + c.bounce_count, 0);
951
+ const totalClicks = mockDb.campaigns.reduce((acc, c) => acc + c.click_count, 0);
952
+ const totalLeads = mockDb.campaigns.reduce((acc, c) => acc + c.lead_count, 0);
953
+
954
+ res.json({
955
+ total_campaigns: mockDb.campaigns.length,
956
+ total_leads: totalLeads,
957
+ total_sent: totalSent,
958
+ total_opens: totalOpens,
959
+ total_replies: totalReplies,
960
+ total_bounces: totalBounces,
961
+ total_clicks: totalClicks,
962
+ open_rate: ((totalOpens / (totalSent || 1)) * 100).toFixed(1),
963
+ reply_rate: ((totalReplies / (totalSent || 1)) * 100).toFixed(1),
964
+ click_rate: ((totalClicks / (totalSent || 1)) * 100).toFixed(1),
965
+ bounce_rate: ((totalBounces / (totalSent || 1)) * 100).toFixed(1)
966
+ });
967
+ });
968
+
969
+ // ==========================================
970
+ // MODULE 8: CLIENT MANAGEMENT API
971
+ // ==========================================
972
+ app.get("/api/clients", (req, res) => res.json(mockDb.clients));
973
+
974
+ app.post("/api/ai/completion", async (req, res) => {
975
+ try {
976
+ const { prompt } = req.body;
977
+ if (!prompt) {
978
+ return res.status(400).json({ error: "Prompt is required" });
979
+ }
980
+ const response = await getAiCompletion(prompt);
981
+ res.json({ response });
982
+ } catch (error) {
983
+ res.status(500).json({ error: "Failed to get AI completion" });
984
+ }
985
+ });
986
+
987
+
988
+ app.post("/api/clients", (req, res) => {
989
+ const newClient = {
990
+ id: "cli_" + Math.random().toString(36).substring(2, 7),
991
+ name: req.body.name || "New Client",
992
+ email: req.body.email || "client@company.com",
993
+ company: req.body.company || "Client Company Ltd",
994
+ campaigns_count: 0,
995
+ leads_count: 0,
996
+ created_at: new Date().toISOString()
997
+ };
998
+ mockDb.clients.push(newClient);
999
+ res.status(201).json(newClient);
1000
+ });
1001
+
1002
+ app.delete("/api/clients/:id", (req, res) => {
1003
+ mockDb.clients = mockDb.clients.filter(c => c.id !== req.params.id);
1004
+ res.json({ success: true });
1005
+ });
1006
+
1007
+ // Liveness probe (Docker HEALTHCHECK + Hugging Face). Must stay BEFORE
1008
+ // the /api proxy catch-all below so it is never forwarded to Smartlead.
1009
+ app.get("/api/health", (_req, res) => {
1010
+ res.json({
1011
+ status: "healthy",
1012
+ app: "BRIDGE Modul-E",
1013
+ version: "1.0.0",
1014
+ mode: process.env.NODE_ENV === "production" ? "production" : "development",
1015
+ timestamp: Date.now() / 1000,
1016
+ });
1017
+ });
1018
+
1019
+ // Proxy Catch-All for any other direct Smartlead API calls
1020
+ app.use("/api", async (req, res) => {
1021
+ const apiKey = req.headers["x-api-key"] as string;
1022
+ if (!apiKey) {
1023
+ return res.status(401).json({ error: "Missing x-api-key header" });
1024
+ }
1025
+
1026
+ try {
1027
+ const targetUrl = `https://server.smartlead.ai/api/v1${req.path}`;
1028
+ const response = await axios({
1029
+ method: req.method,
1030
+ url: targetUrl,
1031
+ data: Object.keys(req.body).length > 0 ? req.body : undefined,
1032
+ params: { ...req.query, api_key: apiKey },
1033
+ timeout: 10000
1034
+ });
1035
+ res.status(response.status).json(response.data);
1036
+ } catch (error: any) {
1037
+ if (error.response) {
1038
+ res.status(error.response.status).json(error.response.data);
1039
+ } else {
1040
+ res.status(500).json({ error: "Proxy connection error", details: error.message });
1041
+ }
1042
+ }
1043
+ });
1044
+
1045
+ // Vite development middleware or production static serving
1046
+ if (process.env.NODE_ENV !== "production") {
1047
+ const vite = await createViteServer({
1048
+ server: { middlewareMode: true },
1049
+ appType: "spa",
1050
+ });
1051
+ app.use(vite.middlewares);
1052
+ } else {
1053
+ const distPath = path.join(process.cwd(), "dist");
1054
+ app.use(express.static(distPath));
1055
+ app.get("*", (req, res) => {
1056
+ res.sendFile(path.join(distPath, "index.html"));
1057
+ });
1058
+ }
1059
+
1060
+ app.listen(PORT, "0.0.0.0", () => {
1061
+ console.log(`BRIDGEModulE Command Hub Server listening on http://0.0.0.0:${PORT}`);
1062
+ });
1063
+ }
1064
+
1065
+ startServer();