@agenticmail/core 0.2.26
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/LICENSE +35 -0
- package/README.md +465 -0
- package/REFERENCE.md +1219 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/email-worker-template-BOJPKCVB.js +39 -0
- package/dist/index.d.ts +1365 -0
- package/dist/index.js +5816 -0
- package/package.json +69 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1365 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import Database from 'better-sqlite3';
|
|
3
|
+
import { ImapFlow } from 'imapflow';
|
|
4
|
+
|
|
5
|
+
interface SendMailOptions {
|
|
6
|
+
to: string | string[];
|
|
7
|
+
subject: string;
|
|
8
|
+
text?: string;
|
|
9
|
+
html?: string;
|
|
10
|
+
cc?: string | string[];
|
|
11
|
+
bcc?: string | string[];
|
|
12
|
+
replyTo?: string;
|
|
13
|
+
inReplyTo?: string;
|
|
14
|
+
references?: string[];
|
|
15
|
+
attachments?: Attachment[];
|
|
16
|
+
headers?: Record<string, string>;
|
|
17
|
+
/** Display name for the From header, e.g. "Fola from Astrum" */
|
|
18
|
+
fromName?: string;
|
|
19
|
+
}
|
|
20
|
+
interface Attachment {
|
|
21
|
+
filename: string;
|
|
22
|
+
content: Buffer | string;
|
|
23
|
+
contentType?: string;
|
|
24
|
+
encoding?: string;
|
|
25
|
+
}
|
|
26
|
+
interface SendResult {
|
|
27
|
+
messageId: string;
|
|
28
|
+
envelope: {
|
|
29
|
+
from: string;
|
|
30
|
+
to: string[];
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
interface EmailEnvelope {
|
|
34
|
+
uid: number;
|
|
35
|
+
seq: number;
|
|
36
|
+
messageId: string;
|
|
37
|
+
subject: string;
|
|
38
|
+
from: AddressInfo[];
|
|
39
|
+
to: AddressInfo[];
|
|
40
|
+
date: Date;
|
|
41
|
+
flags: Set<string>;
|
|
42
|
+
size: number;
|
|
43
|
+
}
|
|
44
|
+
interface AddressInfo {
|
|
45
|
+
name?: string;
|
|
46
|
+
address: string;
|
|
47
|
+
}
|
|
48
|
+
interface ParsedEmail {
|
|
49
|
+
messageId: string;
|
|
50
|
+
subject: string;
|
|
51
|
+
from: AddressInfo[];
|
|
52
|
+
to: AddressInfo[];
|
|
53
|
+
cc?: AddressInfo[];
|
|
54
|
+
replyTo?: AddressInfo[];
|
|
55
|
+
date: Date;
|
|
56
|
+
text?: string;
|
|
57
|
+
html?: string;
|
|
58
|
+
inReplyTo?: string;
|
|
59
|
+
references?: string[];
|
|
60
|
+
attachments: ParsedAttachment[];
|
|
61
|
+
headers: Map<string, string>;
|
|
62
|
+
}
|
|
63
|
+
interface ParsedAttachment {
|
|
64
|
+
filename: string;
|
|
65
|
+
contentType: string;
|
|
66
|
+
size: number;
|
|
67
|
+
content: Buffer;
|
|
68
|
+
}
|
|
69
|
+
interface MailboxInfo {
|
|
70
|
+
name: string;
|
|
71
|
+
exists: number;
|
|
72
|
+
recent: number;
|
|
73
|
+
unseen: number;
|
|
74
|
+
}
|
|
75
|
+
interface SearchCriteria {
|
|
76
|
+
from?: string;
|
|
77
|
+
to?: string;
|
|
78
|
+
subject?: string;
|
|
79
|
+
since?: Date;
|
|
80
|
+
before?: Date;
|
|
81
|
+
seen?: boolean;
|
|
82
|
+
text?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface InboxNewEvent {
|
|
86
|
+
type: 'new';
|
|
87
|
+
uid: number;
|
|
88
|
+
message?: ParsedEmail;
|
|
89
|
+
}
|
|
90
|
+
interface InboxExpungeEvent {
|
|
91
|
+
type: 'expunge';
|
|
92
|
+
seq: number;
|
|
93
|
+
}
|
|
94
|
+
interface InboxFlagsEvent {
|
|
95
|
+
type: 'flags';
|
|
96
|
+
uid: number;
|
|
97
|
+
flags: Set<string>;
|
|
98
|
+
}
|
|
99
|
+
type InboxEvent = InboxNewEvent | InboxExpungeEvent | InboxFlagsEvent;
|
|
100
|
+
interface WatcherOptions {
|
|
101
|
+
mailbox?: string;
|
|
102
|
+
autoFetch?: boolean;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface InboxWatcherOptions {
|
|
106
|
+
host: string;
|
|
107
|
+
port: number;
|
|
108
|
+
email: string;
|
|
109
|
+
password: string;
|
|
110
|
+
secure?: boolean;
|
|
111
|
+
}
|
|
112
|
+
declare class InboxWatcher extends EventEmitter {
|
|
113
|
+
private options;
|
|
114
|
+
private client;
|
|
115
|
+
private watching;
|
|
116
|
+
private mailbox;
|
|
117
|
+
private autoFetch;
|
|
118
|
+
private _lock;
|
|
119
|
+
constructor(options: InboxWatcherOptions, watcherOptions?: WatcherOptions);
|
|
120
|
+
start(): Promise<void>;
|
|
121
|
+
stop(): Promise<void>;
|
|
122
|
+
isWatching(): boolean;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
interface AgenticMailClientOptions {
|
|
126
|
+
agentId: string;
|
|
127
|
+
apiKey: string;
|
|
128
|
+
email?: string;
|
|
129
|
+
password?: string;
|
|
130
|
+
smtp?: {
|
|
131
|
+
host: string;
|
|
132
|
+
port: number;
|
|
133
|
+
};
|
|
134
|
+
imap?: {
|
|
135
|
+
host: string;
|
|
136
|
+
port: number;
|
|
137
|
+
};
|
|
138
|
+
apiUrl?: string;
|
|
139
|
+
}
|
|
140
|
+
declare class AgenticMailClient {
|
|
141
|
+
private sender;
|
|
142
|
+
private receiver;
|
|
143
|
+
private options;
|
|
144
|
+
private connected;
|
|
145
|
+
private connectPromise;
|
|
146
|
+
constructor(options: AgenticMailClientOptions);
|
|
147
|
+
connect(): Promise<void>;
|
|
148
|
+
private _connect;
|
|
149
|
+
disconnect(): Promise<void>;
|
|
150
|
+
send(mail: SendMailOptions): Promise<SendResult>;
|
|
151
|
+
inbox(options?: {
|
|
152
|
+
limit?: number;
|
|
153
|
+
offset?: number;
|
|
154
|
+
}): Promise<EmailEnvelope[]>;
|
|
155
|
+
read(uid: number): Promise<ParsedEmail>;
|
|
156
|
+
search(criteria: SearchCriteria): Promise<number[]>;
|
|
157
|
+
markSeen(uid: number): Promise<void>;
|
|
158
|
+
deleteMessage(uid: number): Promise<void>;
|
|
159
|
+
watch(options?: WatcherOptions): InboxWatcher;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
interface AgenticMailConfig {
|
|
163
|
+
stalwart: {
|
|
164
|
+
url: string;
|
|
165
|
+
adminUser: string;
|
|
166
|
+
adminPassword: string;
|
|
167
|
+
};
|
|
168
|
+
smtp: {
|
|
169
|
+
host: string;
|
|
170
|
+
port: number;
|
|
171
|
+
};
|
|
172
|
+
imap: {
|
|
173
|
+
host: string;
|
|
174
|
+
port: number;
|
|
175
|
+
};
|
|
176
|
+
api: {
|
|
177
|
+
port: number;
|
|
178
|
+
host: string;
|
|
179
|
+
};
|
|
180
|
+
gateway?: {
|
|
181
|
+
mode?: 'relay' | 'domain' | 'none';
|
|
182
|
+
autoResume?: boolean;
|
|
183
|
+
};
|
|
184
|
+
masterKey: string;
|
|
185
|
+
dataDir: string;
|
|
186
|
+
}
|
|
187
|
+
declare function resolveConfig(overrides?: Partial<AgenticMailConfig>): AgenticMailConfig;
|
|
188
|
+
declare function ensureDataDir(config: AgenticMailConfig): void;
|
|
189
|
+
declare function saveConfig(config: AgenticMailConfig): void;
|
|
190
|
+
|
|
191
|
+
interface StalwartPrincipal {
|
|
192
|
+
type: 'individual' | 'group' | 'domain' | 'list' | 'apiKey';
|
|
193
|
+
name: string;
|
|
194
|
+
secrets?: string[];
|
|
195
|
+
emails?: string[];
|
|
196
|
+
description?: string;
|
|
197
|
+
quota?: number;
|
|
198
|
+
memberOf?: string[];
|
|
199
|
+
members?: string[];
|
|
200
|
+
roles?: string[];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
interface StalwartAdminOptions {
|
|
204
|
+
url: string;
|
|
205
|
+
adminUser: string;
|
|
206
|
+
adminPassword: string;
|
|
207
|
+
}
|
|
208
|
+
declare class StalwartAdmin {
|
|
209
|
+
private options;
|
|
210
|
+
private baseUrl;
|
|
211
|
+
private authHeader;
|
|
212
|
+
constructor(options: StalwartAdminOptions);
|
|
213
|
+
private request;
|
|
214
|
+
createPrincipal(principal: StalwartPrincipal): Promise<void>;
|
|
215
|
+
getPrincipal(name: string): Promise<StalwartPrincipal>;
|
|
216
|
+
updatePrincipal(name: string, changes: Partial<StalwartPrincipal>): Promise<void>;
|
|
217
|
+
/** Add an email alias to a principal without removing existing ones */
|
|
218
|
+
addEmailAlias(name: string, email: string): Promise<void>;
|
|
219
|
+
deletePrincipal(name: string): Promise<void>;
|
|
220
|
+
listPrincipals(type?: string): Promise<string[]>;
|
|
221
|
+
/** Ensure a domain exists in Stalwart (create if missing). */
|
|
222
|
+
ensureDomain(domain: string): Promise<void>;
|
|
223
|
+
healthCheck(): Promise<boolean>;
|
|
224
|
+
/** Get a Stalwart setting value. */
|
|
225
|
+
getSetting(key: string): Promise<string | undefined>;
|
|
226
|
+
/** Get all settings under a prefix. */
|
|
227
|
+
getSettings(prefix: string): Promise<Record<string, string>>;
|
|
228
|
+
/**
|
|
229
|
+
* Set a Stalwart configuration value via stalwart-cli.
|
|
230
|
+
* Note: stalwart-cli may return a 500 error even when the operation succeeds.
|
|
231
|
+
* We verify by listing the config afterwards.
|
|
232
|
+
*/
|
|
233
|
+
updateSetting(key: string, value: string): Promise<void>;
|
|
234
|
+
/**
|
|
235
|
+
* Set the server hostname used in SMTP EHLO greetings.
|
|
236
|
+
* Critical for email deliverability — must match the sending domain.
|
|
237
|
+
*/
|
|
238
|
+
setHostname(domain: string): Promise<void>;
|
|
239
|
+
/** Path to the host-side stalwart.toml (mounted read-only into container) */
|
|
240
|
+
private get configPath();
|
|
241
|
+
/** Path to host-side DKIM key directory */
|
|
242
|
+
private get dkimDir();
|
|
243
|
+
/**
|
|
244
|
+
* Create/reuse a DKIM signing key for a domain.
|
|
245
|
+
* Uses stalwart-cli to generate the key and store config in Stalwart's DB.
|
|
246
|
+
* Returns the public key (base64, no headers) for DNS TXT record.
|
|
247
|
+
*/
|
|
248
|
+
createDkimSignature(domain: string, selector?: string): Promise<{
|
|
249
|
+
signatureId: string;
|
|
250
|
+
publicKey: string;
|
|
251
|
+
}>;
|
|
252
|
+
/**
|
|
253
|
+
* Restart the Stalwart Docker container and wait for it to be ready.
|
|
254
|
+
*/
|
|
255
|
+
private restartContainer;
|
|
256
|
+
/**
|
|
257
|
+
* Check if a DKIM signature exists for a domain (checks Stalwart DB).
|
|
258
|
+
*/
|
|
259
|
+
hasDkimSignature(domain: string): Promise<boolean>;
|
|
260
|
+
/**
|
|
261
|
+
* Configure Gmail SMTP as outbound relay (smarthost).
|
|
262
|
+
* Routes all non-local mail through smtp.gmail.com using app password auth.
|
|
263
|
+
* This bypasses the need for a PTR record on the sending IP.
|
|
264
|
+
*/
|
|
265
|
+
configureOutboundRelay(config: {
|
|
266
|
+
smtpHost: string;
|
|
267
|
+
smtpPort: number;
|
|
268
|
+
username: string;
|
|
269
|
+
password: string;
|
|
270
|
+
routeName?: string;
|
|
271
|
+
}): Promise<void>;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Predefined agent roles */
|
|
275
|
+
type AgentRole = 'secretary' | 'assistant' | 'researcher' | 'writer' | 'custom';
|
|
276
|
+
declare const AGENT_ROLES: readonly AgentRole[];
|
|
277
|
+
declare const DEFAULT_AGENT_ROLE: AgentRole;
|
|
278
|
+
declare const DEFAULT_AGENT_NAME = "secretary";
|
|
279
|
+
interface Agent {
|
|
280
|
+
id: string;
|
|
281
|
+
name: string;
|
|
282
|
+
email: string;
|
|
283
|
+
apiKey: string;
|
|
284
|
+
stalwartPrincipal: string;
|
|
285
|
+
createdAt: string;
|
|
286
|
+
updatedAt: string;
|
|
287
|
+
metadata: Record<string, unknown>;
|
|
288
|
+
role: AgentRole;
|
|
289
|
+
}
|
|
290
|
+
interface CreateAgentOptions {
|
|
291
|
+
name: string;
|
|
292
|
+
domain?: string;
|
|
293
|
+
password?: string;
|
|
294
|
+
metadata?: Record<string, unknown>;
|
|
295
|
+
gateway?: 'relay' | 'domain';
|
|
296
|
+
role?: AgentRole;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
declare class AccountManager {
|
|
300
|
+
private db;
|
|
301
|
+
private stalwart;
|
|
302
|
+
constructor(db: Database.Database, stalwart: StalwartAdmin);
|
|
303
|
+
create(options: CreateAgentOptions): Promise<Agent>;
|
|
304
|
+
getById(id: string): Promise<Agent | null>;
|
|
305
|
+
getByApiKey(apiKey: string): Promise<Agent | null>;
|
|
306
|
+
getByName(name: string): Promise<Agent | null>;
|
|
307
|
+
list(): Promise<Agent[]>;
|
|
308
|
+
getByRole(role: AgentRole): Promise<Agent[]>;
|
|
309
|
+
delete(id: string): Promise<boolean>;
|
|
310
|
+
updateMetadata(id: string, metadata: Record<string, unknown>): Promise<Agent | null>;
|
|
311
|
+
getCredentials(id: string): Promise<{
|
|
312
|
+
email: string;
|
|
313
|
+
password: string;
|
|
314
|
+
principal: string;
|
|
315
|
+
smtpHost: string;
|
|
316
|
+
smtpPort: number;
|
|
317
|
+
imapHost: string;
|
|
318
|
+
imapPort: number;
|
|
319
|
+
} | null>;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
interface ArchivedEmail {
|
|
323
|
+
uid: number;
|
|
324
|
+
messageId: string;
|
|
325
|
+
from: string;
|
|
326
|
+
to: string[];
|
|
327
|
+
subject: string;
|
|
328
|
+
date: string;
|
|
329
|
+
text?: string;
|
|
330
|
+
html?: string;
|
|
331
|
+
}
|
|
332
|
+
interface DeletionReport {
|
|
333
|
+
id: string;
|
|
334
|
+
agent: {
|
|
335
|
+
id: string;
|
|
336
|
+
name: string;
|
|
337
|
+
email: string;
|
|
338
|
+
role: string;
|
|
339
|
+
createdAt: string;
|
|
340
|
+
};
|
|
341
|
+
deletedAt: string;
|
|
342
|
+
deletedBy: string;
|
|
343
|
+
reason?: string;
|
|
344
|
+
emails: {
|
|
345
|
+
inbox: ArchivedEmail[];
|
|
346
|
+
sent: ArchivedEmail[];
|
|
347
|
+
other: Record<string, ArchivedEmail[]>;
|
|
348
|
+
};
|
|
349
|
+
summary: {
|
|
350
|
+
totalEmails: number;
|
|
351
|
+
inboxCount: number;
|
|
352
|
+
sentCount: number;
|
|
353
|
+
otherCount: number;
|
|
354
|
+
folders: string[];
|
|
355
|
+
firstEmailDate?: string;
|
|
356
|
+
lastEmailDate?: string;
|
|
357
|
+
topCorrespondents: {
|
|
358
|
+
address: string;
|
|
359
|
+
count: number;
|
|
360
|
+
}[];
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
interface DeletionSummary {
|
|
364
|
+
id: string;
|
|
365
|
+
agentName: string;
|
|
366
|
+
agentEmail: string;
|
|
367
|
+
agentRole: string | null;
|
|
368
|
+
deletedAt: string;
|
|
369
|
+
deletedBy: string | null;
|
|
370
|
+
reason: string | null;
|
|
371
|
+
emailCount: number;
|
|
372
|
+
filePath: string | null;
|
|
373
|
+
}
|
|
374
|
+
interface ArchiveAndDeleteOptions {
|
|
375
|
+
deletedBy?: string;
|
|
376
|
+
reason?: string;
|
|
377
|
+
}
|
|
378
|
+
declare class AgentDeletionService {
|
|
379
|
+
private db;
|
|
380
|
+
private accountManager;
|
|
381
|
+
private config;
|
|
382
|
+
constructor(db: Database.Database, accountManager: AccountManager, config: AgenticMailConfig);
|
|
383
|
+
archiveAndDelete(agentId: string, options?: ArchiveAndDeleteOptions): Promise<DeletionReport>;
|
|
384
|
+
getReport(deletionId: string): DeletionReport | null;
|
|
385
|
+
listReports(): DeletionSummary[];
|
|
386
|
+
private archiveEmails;
|
|
387
|
+
private archiveFolder;
|
|
388
|
+
private buildSummary;
|
|
389
|
+
private saveToFile;
|
|
390
|
+
private saveToDatabase;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
interface MailSenderOptions {
|
|
394
|
+
host: string;
|
|
395
|
+
port: number;
|
|
396
|
+
email: string;
|
|
397
|
+
password: string;
|
|
398
|
+
authUser?: string;
|
|
399
|
+
secure?: boolean;
|
|
400
|
+
}
|
|
401
|
+
interface SendResultWithRaw extends SendResult {
|
|
402
|
+
/** Raw RFC822 message bytes (for appending to Sent folder) */
|
|
403
|
+
raw: Buffer;
|
|
404
|
+
}
|
|
405
|
+
declare class MailSender {
|
|
406
|
+
private options;
|
|
407
|
+
private transporter;
|
|
408
|
+
private email;
|
|
409
|
+
constructor(options: MailSenderOptions);
|
|
410
|
+
send(mail: SendMailOptions): Promise<SendResultWithRaw>;
|
|
411
|
+
verify(): Promise<boolean>;
|
|
412
|
+
close(): void;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
interface MailReceiverOptions {
|
|
416
|
+
host: string;
|
|
417
|
+
port: number;
|
|
418
|
+
email: string;
|
|
419
|
+
password: string;
|
|
420
|
+
secure?: boolean;
|
|
421
|
+
}
|
|
422
|
+
declare class MailReceiver {
|
|
423
|
+
private options;
|
|
424
|
+
private client;
|
|
425
|
+
private connected;
|
|
426
|
+
constructor(options: MailReceiverOptions);
|
|
427
|
+
connect(): Promise<void>;
|
|
428
|
+
/** Check if the IMAP client is actually usable */
|
|
429
|
+
get usable(): boolean;
|
|
430
|
+
disconnect(): Promise<void>;
|
|
431
|
+
getMailboxInfo(mailbox?: string): Promise<MailboxInfo>;
|
|
432
|
+
listEnvelopes(mailbox?: string, options?: {
|
|
433
|
+
limit?: number;
|
|
434
|
+
offset?: number;
|
|
435
|
+
}): Promise<EmailEnvelope[]>;
|
|
436
|
+
fetchMessage(uid: number, mailbox?: string): Promise<Buffer>;
|
|
437
|
+
search(criteria: SearchCriteria, mailbox?: string): Promise<number[]>;
|
|
438
|
+
markSeen(uid: number, mailbox?: string): Promise<void>;
|
|
439
|
+
deleteMessage(uid: number, mailbox?: string): Promise<void>;
|
|
440
|
+
/** Mark a message as unseen (unread) */
|
|
441
|
+
markUnseen(uid: number, mailbox?: string): Promise<void>;
|
|
442
|
+
/** Move a message to another folder */
|
|
443
|
+
moveMessage(uid: number, fromMailbox: string, toMailbox: string): Promise<void>;
|
|
444
|
+
/** List all IMAP folders/mailboxes */
|
|
445
|
+
listFolders(): Promise<FolderInfo[]>;
|
|
446
|
+
/** Create a new IMAP folder */
|
|
447
|
+
createFolder(path: string): Promise<void>;
|
|
448
|
+
/** Batch mark multiple messages as seen */
|
|
449
|
+
batchMarkSeen(uids: number[], mailbox?: string): Promise<void>;
|
|
450
|
+
/** Batch mark multiple messages as unseen */
|
|
451
|
+
batchMarkUnseen(uids: number[], mailbox?: string): Promise<void>;
|
|
452
|
+
/** Batch delete multiple messages */
|
|
453
|
+
batchDelete(uids: number[], mailbox?: string): Promise<void>;
|
|
454
|
+
/** Batch fetch raw message content for multiple UIDs */
|
|
455
|
+
batchFetch(uids: number[], mailbox?: string): Promise<Map<number, Buffer>>;
|
|
456
|
+
/** Batch move multiple messages to another folder */
|
|
457
|
+
batchMove(uids: number[], fromMailbox: string, toMailbox: string): Promise<void>;
|
|
458
|
+
/** Append a raw RFC822 message to a mailbox (e.g. "Sent") with given flags */
|
|
459
|
+
appendMessage(raw: Buffer, mailbox: string, flags?: string[]): Promise<void>;
|
|
460
|
+
getImapClient(): ImapFlow;
|
|
461
|
+
}
|
|
462
|
+
interface FolderInfo {
|
|
463
|
+
path: string;
|
|
464
|
+
name: string;
|
|
465
|
+
specialUse?: string;
|
|
466
|
+
flags: string[];
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
declare function parseEmail(raw: Buffer | string): Promise<ParsedEmail>;
|
|
470
|
+
|
|
471
|
+
type SpamCategory = 'prompt_injection' | 'social_engineering' | 'data_exfiltration' | 'phishing' | 'header_anomaly' | 'content_spam' | 'link_analysis' | 'authentication' | 'attachment_risk';
|
|
472
|
+
interface SpamRuleMatch {
|
|
473
|
+
ruleId: string;
|
|
474
|
+
category: SpamCategory;
|
|
475
|
+
score: number;
|
|
476
|
+
description: string;
|
|
477
|
+
}
|
|
478
|
+
interface SpamResult {
|
|
479
|
+
score: number;
|
|
480
|
+
isSpam: boolean;
|
|
481
|
+
isWarning: boolean;
|
|
482
|
+
matches: SpamRuleMatch[];
|
|
483
|
+
topCategory: SpamCategory | null;
|
|
484
|
+
}
|
|
485
|
+
declare const SPAM_THRESHOLD = 40;
|
|
486
|
+
declare const WARNING_THRESHOLD = 20;
|
|
487
|
+
declare function isInternalEmail(email: ParsedEmail, localDomains?: string[]): boolean;
|
|
488
|
+
declare function scoreEmail(email: ParsedEmail): SpamResult;
|
|
489
|
+
|
|
490
|
+
interface SanitizeDetection {
|
|
491
|
+
type: string;
|
|
492
|
+
description: string;
|
|
493
|
+
count: number;
|
|
494
|
+
}
|
|
495
|
+
interface SanitizeResult {
|
|
496
|
+
text: string;
|
|
497
|
+
html: string;
|
|
498
|
+
detections: SanitizeDetection[];
|
|
499
|
+
wasModified: boolean;
|
|
500
|
+
}
|
|
501
|
+
declare function sanitizeEmail(email: ParsedEmail): SanitizeResult;
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Outbound Email Guard — scans outgoing emails for sensitive content.
|
|
505
|
+
* Pure functions, zero external dependencies.
|
|
506
|
+
*/
|
|
507
|
+
type OutboundCategory = 'pii' | 'credential' | 'system_internal' | 'owner_privacy' | 'attachment_risk';
|
|
508
|
+
type Severity = 'high' | 'medium';
|
|
509
|
+
interface OutboundWarning {
|
|
510
|
+
category: OutboundCategory;
|
|
511
|
+
severity: Severity;
|
|
512
|
+
ruleId: string;
|
|
513
|
+
description: string;
|
|
514
|
+
match: string;
|
|
515
|
+
}
|
|
516
|
+
interface OutboundScanResult {
|
|
517
|
+
warnings: OutboundWarning[];
|
|
518
|
+
hasHighSeverity: boolean;
|
|
519
|
+
hasMediumSeverity: boolean;
|
|
520
|
+
blocked: boolean;
|
|
521
|
+
summary: string;
|
|
522
|
+
}
|
|
523
|
+
interface OutboundScanInput {
|
|
524
|
+
to: string | string[];
|
|
525
|
+
subject?: string;
|
|
526
|
+
text?: string;
|
|
527
|
+
html?: string;
|
|
528
|
+
attachments?: Array<{
|
|
529
|
+
filename?: string;
|
|
530
|
+
contentType?: string;
|
|
531
|
+
content?: string | Buffer;
|
|
532
|
+
encoding?: string;
|
|
533
|
+
}>;
|
|
534
|
+
}
|
|
535
|
+
interface AttachmentAdvisory {
|
|
536
|
+
filename: string;
|
|
537
|
+
risk: string;
|
|
538
|
+
detail: string;
|
|
539
|
+
}
|
|
540
|
+
interface LinkAdvisory {
|
|
541
|
+
ruleId: string;
|
|
542
|
+
detail: string;
|
|
543
|
+
}
|
|
544
|
+
interface SecurityAdvisory {
|
|
545
|
+
spamScore?: number;
|
|
546
|
+
spamCategory?: string;
|
|
547
|
+
isSpam?: boolean;
|
|
548
|
+
isWarning?: boolean;
|
|
549
|
+
attachmentWarnings: AttachmentAdvisory[];
|
|
550
|
+
linkWarnings: LinkAdvisory[];
|
|
551
|
+
summary: string;
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Scans outgoing email content for sensitive data (PII, credentials, system info, owner privacy).
|
|
555
|
+
* Skips scanning entirely if ALL recipients are @localhost (internal agent-to-agent communication).
|
|
556
|
+
*/
|
|
557
|
+
declare function scanOutboundEmail(input: OutboundScanInput): OutboundScanResult;
|
|
558
|
+
/**
|
|
559
|
+
* Builds a structured security advisory from email metadata (spam score, attachments, link warnings).
|
|
560
|
+
* Used by tool handlers to present per-attachment and per-link warnings to the agent.
|
|
561
|
+
*/
|
|
562
|
+
declare function buildInboundSecurityAdvisory(security: {
|
|
563
|
+
score?: number;
|
|
564
|
+
category?: string;
|
|
565
|
+
isSpam?: boolean;
|
|
566
|
+
isWarning?: boolean;
|
|
567
|
+
matches?: Array<{
|
|
568
|
+
ruleId: string;
|
|
569
|
+
}>;
|
|
570
|
+
} | undefined, attachments: Array<{
|
|
571
|
+
filename?: string;
|
|
572
|
+
contentType?: string;
|
|
573
|
+
size?: number;
|
|
574
|
+
}> | undefined): SecurityAdvisory;
|
|
575
|
+
|
|
576
|
+
declare function getDatabase(config: AgenticMailConfig): Database.Database;
|
|
577
|
+
declare function closeDatabase(): void;
|
|
578
|
+
declare function createTestDatabase(): Database.Database;
|
|
579
|
+
|
|
580
|
+
interface SearchableEmail {
|
|
581
|
+
agentId: string;
|
|
582
|
+
messageId: string;
|
|
583
|
+
subject: string;
|
|
584
|
+
fromAddress: string;
|
|
585
|
+
toAddress: string;
|
|
586
|
+
bodyText: string;
|
|
587
|
+
receivedAt: string;
|
|
588
|
+
}
|
|
589
|
+
declare class EmailSearchIndex {
|
|
590
|
+
private db;
|
|
591
|
+
constructor(db: Database.Database);
|
|
592
|
+
index(email: SearchableEmail): void;
|
|
593
|
+
search(agentId: string, query: string, limit?: number): SearchableEmail[];
|
|
594
|
+
deleteByAgent(agentId: string): void;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
interface DomainInfo {
|
|
598
|
+
domain: string;
|
|
599
|
+
stalwartPrincipal: string;
|
|
600
|
+
dkimSelector?: string;
|
|
601
|
+
dkimPublicKey?: string;
|
|
602
|
+
verified: boolean;
|
|
603
|
+
createdAt: string;
|
|
604
|
+
}
|
|
605
|
+
interface DnsRecord {
|
|
606
|
+
type: 'TXT' | 'CNAME' | 'MX';
|
|
607
|
+
name: string;
|
|
608
|
+
value: string;
|
|
609
|
+
purpose: string;
|
|
610
|
+
}
|
|
611
|
+
interface DomainSetupResult {
|
|
612
|
+
domain: string;
|
|
613
|
+
dnsRecords: DnsRecord[];
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
declare class DomainManager {
|
|
617
|
+
private db;
|
|
618
|
+
private stalwart;
|
|
619
|
+
constructor(db: Database.Database, stalwart: StalwartAdmin);
|
|
620
|
+
setup(domain: string): Promise<DomainSetupResult>;
|
|
621
|
+
private generateDnsRecords;
|
|
622
|
+
get(domain: string): Promise<DomainInfo | null>;
|
|
623
|
+
list(): Promise<DomainInfo[]>;
|
|
624
|
+
getDnsRecords(domain: string): Promise<DnsRecord[]>;
|
|
625
|
+
verify(domain: string): Promise<boolean>;
|
|
626
|
+
delete(domain: string): Promise<boolean>;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
type GatewayMode = 'relay' | 'domain' | 'none';
|
|
630
|
+
type RelayProvider = 'gmail' | 'outlook' | 'custom';
|
|
631
|
+
interface RelayConfig {
|
|
632
|
+
provider: RelayProvider;
|
|
633
|
+
email: string;
|
|
634
|
+
password: string;
|
|
635
|
+
smtpHost: string;
|
|
636
|
+
smtpPort: number;
|
|
637
|
+
imapHost: string;
|
|
638
|
+
imapPort: number;
|
|
639
|
+
}
|
|
640
|
+
interface DomainModeConfig {
|
|
641
|
+
domain: string;
|
|
642
|
+
cloudflareApiToken: string;
|
|
643
|
+
cloudflareAccountId: string;
|
|
644
|
+
tunnelId?: string;
|
|
645
|
+
tunnelToken?: string;
|
|
646
|
+
/** URL of the Cloudflare Worker outbound relay (e.g. https://agenticmail-outbound.xxx.workers.dev) */
|
|
647
|
+
outboundWorkerUrl?: string;
|
|
648
|
+
/** Shared secret for authenticating with the outbound Worker */
|
|
649
|
+
outboundSecret?: string;
|
|
650
|
+
/** Shared secret for authenticating inbound webhook from Email Worker */
|
|
651
|
+
inboundSecret?: string;
|
|
652
|
+
/** Name of the deployed Email Worker */
|
|
653
|
+
emailWorkerName?: string;
|
|
654
|
+
}
|
|
655
|
+
interface GatewayConfig {
|
|
656
|
+
mode: GatewayMode;
|
|
657
|
+
relay?: RelayConfig;
|
|
658
|
+
domain?: DomainModeConfig;
|
|
659
|
+
}
|
|
660
|
+
interface GatewayStatus {
|
|
661
|
+
mode: GatewayMode;
|
|
662
|
+
healthy: boolean;
|
|
663
|
+
relay?: {
|
|
664
|
+
provider: RelayProvider;
|
|
665
|
+
email: string;
|
|
666
|
+
polling: boolean;
|
|
667
|
+
};
|
|
668
|
+
domain?: {
|
|
669
|
+
domain: string;
|
|
670
|
+
dnsConfigured: boolean;
|
|
671
|
+
tunnelActive: boolean;
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
interface PurchasedDomain {
|
|
675
|
+
domain: string;
|
|
676
|
+
registrar: string;
|
|
677
|
+
cloudflareZoneId?: string;
|
|
678
|
+
tunnelId?: string;
|
|
679
|
+
dnsConfigured: boolean;
|
|
680
|
+
tunnelActive: boolean;
|
|
681
|
+
purchasedAt: string;
|
|
682
|
+
}
|
|
683
|
+
/** Relay provider presets for SMTP/IMAP connection details */
|
|
684
|
+
declare const RELAY_PRESETS: Record<'gmail' | 'outlook', Pick<RelayConfig, 'smtpHost' | 'smtpPort' | 'imapHost' | 'imapPort'>>;
|
|
685
|
+
interface CloudflareZone {
|
|
686
|
+
id: string;
|
|
687
|
+
name: string;
|
|
688
|
+
status: string;
|
|
689
|
+
name_servers: string[];
|
|
690
|
+
}
|
|
691
|
+
interface CloudflareDnsRecord {
|
|
692
|
+
id: string;
|
|
693
|
+
type: string;
|
|
694
|
+
name: string;
|
|
695
|
+
content: string;
|
|
696
|
+
ttl: number;
|
|
697
|
+
proxied?: boolean;
|
|
698
|
+
priority?: number;
|
|
699
|
+
}
|
|
700
|
+
interface CloudflareTunnel {
|
|
701
|
+
id: string;
|
|
702
|
+
name: string;
|
|
703
|
+
status: string;
|
|
704
|
+
created_at: string;
|
|
705
|
+
deleted_at?: string | null;
|
|
706
|
+
connections: Array<{
|
|
707
|
+
id: string;
|
|
708
|
+
}>;
|
|
709
|
+
}
|
|
710
|
+
interface CloudflareDomainAvailability {
|
|
711
|
+
name: string;
|
|
712
|
+
available: boolean;
|
|
713
|
+
premium: boolean;
|
|
714
|
+
price?: number;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
interface RelayGatewayOptions {
|
|
718
|
+
onInboundMail?: (agentName: string, parsed: InboundEmail) => void | Promise<void>;
|
|
719
|
+
/** Fallback agent name for emails without sub-addressing */
|
|
720
|
+
defaultAgentName?: string;
|
|
721
|
+
}
|
|
722
|
+
interface InboundEmail {
|
|
723
|
+
messageId: string;
|
|
724
|
+
from: string;
|
|
725
|
+
to: string;
|
|
726
|
+
subject: string;
|
|
727
|
+
text?: string;
|
|
728
|
+
html?: string;
|
|
729
|
+
date: Date;
|
|
730
|
+
inReplyTo?: string;
|
|
731
|
+
references?: string[];
|
|
732
|
+
attachments?: Array<{
|
|
733
|
+
filename: string;
|
|
734
|
+
contentType: string;
|
|
735
|
+
size: number;
|
|
736
|
+
content: Buffer;
|
|
737
|
+
}>;
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* RelayGateway handles sending/receiving email through an existing
|
|
741
|
+
* Gmail/Outlook account using sub-addressing (user+agent@gmail.com).
|
|
742
|
+
*/
|
|
743
|
+
declare class RelayGateway {
|
|
744
|
+
private smtpTransport;
|
|
745
|
+
private pollTimer;
|
|
746
|
+
private polling;
|
|
747
|
+
private config;
|
|
748
|
+
private onInboundMail;
|
|
749
|
+
private defaultAgentName;
|
|
750
|
+
private _pollInProgress;
|
|
751
|
+
/** Track highest UID seen so we only process new messages after first poll */
|
|
752
|
+
private lastSeenUid;
|
|
753
|
+
private firstPollDone;
|
|
754
|
+
/** Callback invoked when lastSeenUid advances (for persistence) */
|
|
755
|
+
onUidAdvance: ((uid: number) => void) | null;
|
|
756
|
+
/** Map sent messageId → agentName for In-Reply-To based routing */
|
|
757
|
+
private sentMessageIds;
|
|
758
|
+
/** Robustness: consecutive failure tracking + backoff */
|
|
759
|
+
private consecutiveFailures;
|
|
760
|
+
private pollIntervalMs;
|
|
761
|
+
private readonly MAX_BACKOFF_MS;
|
|
762
|
+
private readonly CONNECT_TIMEOUT_MS;
|
|
763
|
+
constructor(options?: RelayGatewayOptions);
|
|
764
|
+
setup(config: RelayConfig): Promise<void>;
|
|
765
|
+
/**
|
|
766
|
+
* Send an email through the relay SMTP server.
|
|
767
|
+
* Rewrites the From address to use sub-addressing: relay+agentName@gmail.com
|
|
768
|
+
*/
|
|
769
|
+
sendViaRelay(agentName: string, mail: SendMailOptions): Promise<SendResultWithRaw>;
|
|
770
|
+
/**
|
|
771
|
+
* Start polling the relay IMAP account for new emails.
|
|
772
|
+
* Routes inbound mail to the correct agent based on sub-addressing or In-Reply-To.
|
|
773
|
+
*
|
|
774
|
+
* Robust design:
|
|
775
|
+
* - Uses setTimeout (not setInterval) so backoff works naturally
|
|
776
|
+
* - Exponential backoff on consecutive failures (30s → 1m → 2m → 5m cap)
|
|
777
|
+
* - Always reschedules — polling never permanently stops
|
|
778
|
+
* - Connection timeout prevents hung connections
|
|
779
|
+
* - Detailed failure logging with recovery info
|
|
780
|
+
*/
|
|
781
|
+
startPolling(intervalMs?: number): Promise<void>;
|
|
782
|
+
stopPolling(): void;
|
|
783
|
+
/** Calculate next poll delay with exponential backoff on failures */
|
|
784
|
+
private getNextPollDelay;
|
|
785
|
+
/** Schedule the next poll with appropriate delay */
|
|
786
|
+
private scheduleNextPoll;
|
|
787
|
+
private pollOnce;
|
|
788
|
+
private _doPoll;
|
|
789
|
+
/**
|
|
790
|
+
* Check if an email address is one of our relay sender addresses (user+agent@domain).
|
|
791
|
+
*/
|
|
792
|
+
private isOurRelaySender;
|
|
793
|
+
/**
|
|
794
|
+
* Extract agent name from email using multiple strategies:
|
|
795
|
+
* 1. Sub-address in To/CC/Delivered-To/X-Original-To headers
|
|
796
|
+
* 2. In-Reply-To matching against sent message IDs
|
|
797
|
+
* 3. References chain matching against sent message IDs
|
|
798
|
+
*/
|
|
799
|
+
private extractAgentName;
|
|
800
|
+
/**
|
|
801
|
+
* Register a sent message ID for reply tracking.
|
|
802
|
+
* Called externally when messages are sent via relay from GatewayManager.
|
|
803
|
+
*/
|
|
804
|
+
trackSentMessage(messageId: string, agentName: string): void;
|
|
805
|
+
/**
|
|
806
|
+
* Restore lastSeenUid from persistent storage (used on resume after restart).
|
|
807
|
+
* If the restored UID is > 0, also marks firstPollDone to skip the initial window scan.
|
|
808
|
+
*/
|
|
809
|
+
setLastSeenUid(uid: number): void;
|
|
810
|
+
isConfigured(): boolean;
|
|
811
|
+
isPolling(): boolean;
|
|
812
|
+
getConfig(): RelayConfig | null;
|
|
813
|
+
/**
|
|
814
|
+
* Search the relay IMAP account (Gmail/Outlook) for emails matching criteria.
|
|
815
|
+
* Returns parsed envelope data so results can be merged with local search.
|
|
816
|
+
*/
|
|
817
|
+
searchRelay(criteria: {
|
|
818
|
+
from?: string;
|
|
819
|
+
to?: string;
|
|
820
|
+
subject?: string;
|
|
821
|
+
text?: string;
|
|
822
|
+
since?: Date;
|
|
823
|
+
before?: Date;
|
|
824
|
+
seen?: boolean;
|
|
825
|
+
}, maxResults?: number): Promise<RelaySearchResult[]>;
|
|
826
|
+
/**
|
|
827
|
+
* Fetch a full email from the relay account by UID and return it as an InboundEmail.
|
|
828
|
+
* Used to import a specific email from Gmail/Outlook into the local inbox.
|
|
829
|
+
*/
|
|
830
|
+
fetchRelayMessage(uid: number): Promise<InboundEmail | null>;
|
|
831
|
+
shutdown(): Promise<void>;
|
|
832
|
+
}
|
|
833
|
+
interface RelaySearchResult {
|
|
834
|
+
uid: number;
|
|
835
|
+
source: 'relay';
|
|
836
|
+
account: string;
|
|
837
|
+
messageId: string;
|
|
838
|
+
subject: string;
|
|
839
|
+
from: Array<{
|
|
840
|
+
name?: string;
|
|
841
|
+
address: string;
|
|
842
|
+
}>;
|
|
843
|
+
to: Array<{
|
|
844
|
+
name?: string;
|
|
845
|
+
address: string;
|
|
846
|
+
}>;
|
|
847
|
+
date: Date;
|
|
848
|
+
flags: string[];
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
declare class CloudflareClient {
|
|
852
|
+
private token;
|
|
853
|
+
private accountId;
|
|
854
|
+
constructor(token: string, accountId: string);
|
|
855
|
+
listZones(): Promise<CloudflareZone[]>;
|
|
856
|
+
getZone(domain: string): Promise<CloudflareZone | null>;
|
|
857
|
+
createZone(domain: string): Promise<CloudflareZone>;
|
|
858
|
+
listDnsRecords(zoneId: string): Promise<CloudflareDnsRecord[]>;
|
|
859
|
+
createDnsRecord(zoneId: string, record: {
|
|
860
|
+
type: string;
|
|
861
|
+
name: string;
|
|
862
|
+
content: string;
|
|
863
|
+
ttl?: number;
|
|
864
|
+
priority?: number;
|
|
865
|
+
proxied?: boolean;
|
|
866
|
+
}): Promise<CloudflareDnsRecord>;
|
|
867
|
+
deleteDnsRecord(zoneId: string, recordId: string): Promise<void>;
|
|
868
|
+
searchDomains(query: string): Promise<CloudflareDomainAvailability[]>;
|
|
869
|
+
checkAvailability(domain: string): Promise<CloudflareDomainAvailability>;
|
|
870
|
+
purchaseDomain(domain: string, autoRenew?: boolean): Promise<{
|
|
871
|
+
domain: string;
|
|
872
|
+
status: string;
|
|
873
|
+
}>;
|
|
874
|
+
listRegisteredDomains(): Promise<Array<{
|
|
875
|
+
domain: string;
|
|
876
|
+
status: string;
|
|
877
|
+
}>>;
|
|
878
|
+
createTunnel(name: string): Promise<CloudflareTunnel>;
|
|
879
|
+
getTunnel(tunnelId: string): Promise<CloudflareTunnel>;
|
|
880
|
+
getTunnelToken(tunnelId: string): Promise<string>;
|
|
881
|
+
createTunnelRoute(tunnelId: string, hostname: string, service: string, options?: {
|
|
882
|
+
apiService?: string;
|
|
883
|
+
}): Promise<void>;
|
|
884
|
+
deleteTunnel(tunnelId: string): Promise<void>;
|
|
885
|
+
/** Enable Email Routing on a zone */
|
|
886
|
+
enableEmailRouting(zoneId: string): Promise<void>;
|
|
887
|
+
/** Disable Email Routing on a zone (unlocks managed MX/SPF records for deletion) */
|
|
888
|
+
disableEmailRouting(zoneId: string): Promise<void>;
|
|
889
|
+
/** Get Email Routing status for a zone */
|
|
890
|
+
getEmailRoutingStatus(zoneId: string): Promise<{
|
|
891
|
+
enabled: boolean;
|
|
892
|
+
status: string;
|
|
893
|
+
}>;
|
|
894
|
+
/** Set catch-all rule to forward to a Worker */
|
|
895
|
+
setCatchAllWorkerRule(zoneId: string, workerName: string): Promise<void>;
|
|
896
|
+
/** Deploy an Email Worker script (ES module format) */
|
|
897
|
+
deployEmailWorker(scriptName: string, scriptContent: string, envVars?: Record<string, string>): Promise<void>;
|
|
898
|
+
/** Delete a Worker script */
|
|
899
|
+
deleteWorker(scriptName: string): Promise<void>;
|
|
900
|
+
listTunnels(): Promise<CloudflareTunnel[]>;
|
|
901
|
+
private request;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
interface DomainSearchResult {
|
|
905
|
+
domain: string;
|
|
906
|
+
available: boolean;
|
|
907
|
+
premium: boolean;
|
|
908
|
+
price?: number;
|
|
909
|
+
}
|
|
910
|
+
interface DomainPurchaseResult {
|
|
911
|
+
domain: string;
|
|
912
|
+
status: string;
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* DomainPurchaser handles searching for and purchasing domains
|
|
916
|
+
* via Cloudflare Registrar.
|
|
917
|
+
*/
|
|
918
|
+
declare class DomainPurchaser {
|
|
919
|
+
private cf;
|
|
920
|
+
constructor(cf: CloudflareClient);
|
|
921
|
+
/**
|
|
922
|
+
* Search for available domains matching the given keywords.
|
|
923
|
+
* Appends common TLDs to keywords and checks availability.
|
|
924
|
+
*/
|
|
925
|
+
searchAvailable(keywords: string[], tlds?: string[]): Promise<DomainSearchResult[]>;
|
|
926
|
+
/**
|
|
927
|
+
* Purchase a domain via Cloudflare Registrar.
|
|
928
|
+
* NOTE: Cloudflare API tokens only support READ access for registrar.
|
|
929
|
+
* Domain purchases must be done manually via the Cloudflare dashboard
|
|
930
|
+
* or another registrar (then point nameservers to Cloudflare).
|
|
931
|
+
*/
|
|
932
|
+
purchase(_domain: string, _autoRenew?: boolean): Promise<DomainPurchaseResult>;
|
|
933
|
+
/**
|
|
934
|
+
* Check the registration status of a purchased domain.
|
|
935
|
+
*/
|
|
936
|
+
getStatus(domain: string): Promise<{
|
|
937
|
+
domain: string;
|
|
938
|
+
status: string;
|
|
939
|
+
}>;
|
|
940
|
+
/**
|
|
941
|
+
* List all domains registered under the Cloudflare account.
|
|
942
|
+
*/
|
|
943
|
+
listRegistered(): Promise<Array<{
|
|
944
|
+
domain: string;
|
|
945
|
+
status: string;
|
|
946
|
+
}>>;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
interface DnsSetupResult {
|
|
950
|
+
records: Array<{
|
|
951
|
+
type: string;
|
|
952
|
+
name: string;
|
|
953
|
+
content: string;
|
|
954
|
+
purpose: string;
|
|
955
|
+
}>;
|
|
956
|
+
removed: Array<{
|
|
957
|
+
type: string;
|
|
958
|
+
name: string;
|
|
959
|
+
content: string;
|
|
960
|
+
reason: string;
|
|
961
|
+
}>;
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* DNSConfigurator automatically creates MX, SPF, DKIM, and DMARC
|
|
965
|
+
* DNS records for a domain using the Cloudflare API.
|
|
966
|
+
* Replaces conflicting records (old MX, SPF, A records) to ensure clean setup.
|
|
967
|
+
*/
|
|
968
|
+
declare class DNSConfigurator {
|
|
969
|
+
private cf;
|
|
970
|
+
constructor(cf: CloudflareClient);
|
|
971
|
+
/**
|
|
972
|
+
* Configure all DNS records required for email on a domain.
|
|
973
|
+
* Replaces existing MX and SPF records that conflict with AgenticMail.
|
|
974
|
+
*/
|
|
975
|
+
/**
|
|
976
|
+
* Detect the server's public IPv4 address for SPF records.
|
|
977
|
+
*/
|
|
978
|
+
detectPublicIp(): Promise<string | null>;
|
|
979
|
+
configureForEmail(domain: string, zoneId: string, options?: {
|
|
980
|
+
dkimSelector?: string;
|
|
981
|
+
dkimPublicKey?: string;
|
|
982
|
+
serverIp?: string;
|
|
983
|
+
}): Promise<DnsSetupResult>;
|
|
984
|
+
/**
|
|
985
|
+
* Configure DNS records to point the domain at a Cloudflare Tunnel.
|
|
986
|
+
* Removes conflicting A/AAAA records for the root domain first.
|
|
987
|
+
*/
|
|
988
|
+
configureForTunnel(domain: string, zoneId: string, tunnelId: string): Promise<DnsSetupResult['removed']>;
|
|
989
|
+
/**
|
|
990
|
+
* Verify DNS propagation by resolving MX and TXT records.
|
|
991
|
+
*/
|
|
992
|
+
verify(domain: string): Promise<{
|
|
993
|
+
mx: boolean;
|
|
994
|
+
spf: boolean;
|
|
995
|
+
dmarc: boolean;
|
|
996
|
+
}>;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
interface TunnelConfig {
|
|
1000
|
+
tunnelId: string;
|
|
1001
|
+
tunnelToken: string;
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* TunnelManager handles the Cloudflare Tunnel lifecycle:
|
|
1005
|
+
* downloading cloudflared, creating/starting tunnels, and routing ingress.
|
|
1006
|
+
*/
|
|
1007
|
+
declare class TunnelManager {
|
|
1008
|
+
private cf;
|
|
1009
|
+
private process;
|
|
1010
|
+
private running;
|
|
1011
|
+
private binPath;
|
|
1012
|
+
constructor(cf: CloudflareClient);
|
|
1013
|
+
/**
|
|
1014
|
+
* Find or download the cloudflared binary.
|
|
1015
|
+
* Checks: managed binary → system-wide → download.
|
|
1016
|
+
*/
|
|
1017
|
+
install(): Promise<string>;
|
|
1018
|
+
/**
|
|
1019
|
+
* Create a new Cloudflare Tunnel via the API.
|
|
1020
|
+
* If a tunnel with the same name already exists, reuse it.
|
|
1021
|
+
*/
|
|
1022
|
+
create(name: string): Promise<TunnelConfig>;
|
|
1023
|
+
/**
|
|
1024
|
+
* Start the cloudflared tunnel process.
|
|
1025
|
+
*/
|
|
1026
|
+
start(tunnelToken: string): Promise<void>;
|
|
1027
|
+
/**
|
|
1028
|
+
* Configure tunnel ingress rules: route mail.{domain} → SMTP, {domain} → HTTP
|
|
1029
|
+
*/
|
|
1030
|
+
createIngress(tunnelId: string, domain: string, smtpPort?: number, httpPort?: number, apiPort?: number): Promise<void>;
|
|
1031
|
+
/**
|
|
1032
|
+
* Stop the running cloudflared process.
|
|
1033
|
+
*/
|
|
1034
|
+
stop(): Promise<void>;
|
|
1035
|
+
/**
|
|
1036
|
+
* Check if the tunnel is currently running.
|
|
1037
|
+
*/
|
|
1038
|
+
status(): {
|
|
1039
|
+
running: boolean;
|
|
1040
|
+
pid?: number;
|
|
1041
|
+
};
|
|
1042
|
+
/**
|
|
1043
|
+
* Check tunnel health via the Cloudflare API.
|
|
1044
|
+
*/
|
|
1045
|
+
healthCheck(tunnelId: string): Promise<{
|
|
1046
|
+
healthy: boolean;
|
|
1047
|
+
status: string;
|
|
1048
|
+
}>;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
interface LocalSmtpConfig {
|
|
1052
|
+
host: string;
|
|
1053
|
+
port: number;
|
|
1054
|
+
user: string;
|
|
1055
|
+
pass: string;
|
|
1056
|
+
}
|
|
1057
|
+
interface GatewayManagerOptions {
|
|
1058
|
+
db: Database.Database;
|
|
1059
|
+
stalwart: StalwartAdmin;
|
|
1060
|
+
accountManager?: AccountManager;
|
|
1061
|
+
localSmtp?: LocalSmtpConfig;
|
|
1062
|
+
onInboundMail?: (agentName: string, mail: InboundEmail) => void | Promise<void>;
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* GatewayManager orchestrates relay and domain modes for sending/receiving
|
|
1066
|
+
* real internet email. It coordinates between the relay gateway, Cloudflare
|
|
1067
|
+
* services (DNS, tunnels, registrar), and the local Stalwart instance.
|
|
1068
|
+
*/
|
|
1069
|
+
declare class GatewayManager {
|
|
1070
|
+
private options;
|
|
1071
|
+
private db;
|
|
1072
|
+
private stalwart;
|
|
1073
|
+
private accountManager;
|
|
1074
|
+
private relay;
|
|
1075
|
+
private config;
|
|
1076
|
+
private cfClient;
|
|
1077
|
+
private tunnel;
|
|
1078
|
+
private dnsConfigurator;
|
|
1079
|
+
private domainPurchaser;
|
|
1080
|
+
constructor(options: GatewayManagerOptions);
|
|
1081
|
+
/**
|
|
1082
|
+
* Check if a message has already been delivered to an agent (deduplication).
|
|
1083
|
+
*/
|
|
1084
|
+
isAlreadyDelivered(messageId: string, agentName: string): boolean;
|
|
1085
|
+
/**
|
|
1086
|
+
* Record that a message was delivered to an agent.
|
|
1087
|
+
*/
|
|
1088
|
+
recordDelivery(messageId: string, agentName: string): void;
|
|
1089
|
+
/**
|
|
1090
|
+
* Built-in inbound mail handler: delivers relay inbound mail to agent's local Stalwart mailbox.
|
|
1091
|
+
* Authenticates as the agent to send to their own mailbox (Stalwart requires sender = auth user).
|
|
1092
|
+
*
|
|
1093
|
+
* Also intercepts owner replies to approval notification emails — if the reply says
|
|
1094
|
+
* "approve" or "reject", the pending outbound email is automatically processed.
|
|
1095
|
+
*/
|
|
1096
|
+
private deliverInboundLocally;
|
|
1097
|
+
/**
|
|
1098
|
+
* Check if an inbound email is a reply to a pending approval notification.
|
|
1099
|
+
* If the reply body starts with "approve"/"yes" or "reject"/"no", automatically
|
|
1100
|
+
* process the pending email (send it or discard it) and confirm to the owner.
|
|
1101
|
+
*/
|
|
1102
|
+
private tryProcessApprovalReply;
|
|
1103
|
+
/**
|
|
1104
|
+
* Execute approval of a pending outbound email: look up the agent, reconstitute
|
|
1105
|
+
* attachments, and send the email via gateway routing or local SMTP.
|
|
1106
|
+
*/
|
|
1107
|
+
private executeApproval;
|
|
1108
|
+
/**
|
|
1109
|
+
* Send a confirmation email back to the owner after processing an approval reply.
|
|
1110
|
+
*/
|
|
1111
|
+
private sendApprovalConfirmation;
|
|
1112
|
+
setupRelay(config: RelayConfig, options?: {
|
|
1113
|
+
defaultAgentName?: string;
|
|
1114
|
+
defaultAgentRole?: AgentRole;
|
|
1115
|
+
skipDefaultAgent?: boolean;
|
|
1116
|
+
}): Promise<{
|
|
1117
|
+
agent?: Agent;
|
|
1118
|
+
}>;
|
|
1119
|
+
setupDomain(options: {
|
|
1120
|
+
cloudflareToken: string;
|
|
1121
|
+
cloudflareAccountId: string;
|
|
1122
|
+
domain?: string;
|
|
1123
|
+
purchase?: {
|
|
1124
|
+
keywords: string[];
|
|
1125
|
+
tld?: string;
|
|
1126
|
+
};
|
|
1127
|
+
outboundWorkerUrl?: string;
|
|
1128
|
+
outboundSecret?: string;
|
|
1129
|
+
gmailRelay?: {
|
|
1130
|
+
email: string;
|
|
1131
|
+
appPassword: string;
|
|
1132
|
+
};
|
|
1133
|
+
}): Promise<{
|
|
1134
|
+
domain: string;
|
|
1135
|
+
dnsConfigured: boolean;
|
|
1136
|
+
tunnelId: string;
|
|
1137
|
+
outboundRelay?: {
|
|
1138
|
+
configured: boolean;
|
|
1139
|
+
provider: string;
|
|
1140
|
+
};
|
|
1141
|
+
nextSteps?: string[];
|
|
1142
|
+
}>;
|
|
1143
|
+
/**
|
|
1144
|
+
* Send a test email through the gateway without requiring a real agent.
|
|
1145
|
+
* In relay mode, uses "test" as the sub-address.
|
|
1146
|
+
* In domain mode, uses the first available agent (Stalwart needs real credentials).
|
|
1147
|
+
*/
|
|
1148
|
+
sendTestEmail(to: string): Promise<SendResultWithRaw | null>;
|
|
1149
|
+
/**
|
|
1150
|
+
* Route an outbound email. If the destination is external and a gateway
|
|
1151
|
+
* is configured, send via the appropriate channel.
|
|
1152
|
+
* Returns null if the mail should be sent via local Stalwart.
|
|
1153
|
+
*/
|
|
1154
|
+
routeOutbound(agentName: string, mail: SendMailOptions): Promise<SendResultWithRaw | null>;
|
|
1155
|
+
/**
|
|
1156
|
+
* Send email by submitting to local Stalwart via SMTP (port 587).
|
|
1157
|
+
* Stalwart handles DKIM signing and delivery (direct or via relay).
|
|
1158
|
+
* Reply-To is set to the agent's domain email so replies come back
|
|
1159
|
+
* to the domain (handled by Cloudflare Email Routing → inbound Worker).
|
|
1160
|
+
*/
|
|
1161
|
+
private sendViaStalwart;
|
|
1162
|
+
getStatus(): GatewayStatus;
|
|
1163
|
+
getMode(): GatewayMode;
|
|
1164
|
+
getConfig(): GatewayConfig;
|
|
1165
|
+
getStalwart(): StalwartAdmin;
|
|
1166
|
+
getDomainPurchaser(): DomainPurchaser | null;
|
|
1167
|
+
getDNSConfigurator(): DNSConfigurator | null;
|
|
1168
|
+
getTunnelManager(): TunnelManager | null;
|
|
1169
|
+
getRelay(): RelayGateway;
|
|
1170
|
+
/**
|
|
1171
|
+
* Search the connected relay account (Gmail/Outlook) for emails matching criteria.
|
|
1172
|
+
* Returns empty array if relay is not configured.
|
|
1173
|
+
*/
|
|
1174
|
+
searchRelay(criteria: {
|
|
1175
|
+
from?: string;
|
|
1176
|
+
to?: string;
|
|
1177
|
+
subject?: string;
|
|
1178
|
+
text?: string;
|
|
1179
|
+
since?: Date;
|
|
1180
|
+
before?: Date;
|
|
1181
|
+
seen?: boolean;
|
|
1182
|
+
}, maxResults?: number): Promise<RelaySearchResult[]>;
|
|
1183
|
+
/**
|
|
1184
|
+
* Import an email from the connected relay account into an agent's local inbox.
|
|
1185
|
+
* Fetches the full message from relay IMAP and delivers it locally, preserving
|
|
1186
|
+
* all headers (Message-ID, In-Reply-To, References) for thread continuity.
|
|
1187
|
+
*/
|
|
1188
|
+
importRelayMessage(relayUid: number, agentName: string): Promise<{
|
|
1189
|
+
success: boolean;
|
|
1190
|
+
error?: string;
|
|
1191
|
+
}>;
|
|
1192
|
+
shutdown(): Promise<void>;
|
|
1193
|
+
/**
|
|
1194
|
+
* Resume gateway from saved config (e.g., after server restart).
|
|
1195
|
+
*/
|
|
1196
|
+
resume(): Promise<void>;
|
|
1197
|
+
private loadConfig;
|
|
1198
|
+
private saveConfig;
|
|
1199
|
+
private saveLastSeenUid;
|
|
1200
|
+
private loadLastSeenUid;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
interface RelayBridgeOptions {
|
|
1204
|
+
/** Port for the HTTP bridge server */
|
|
1205
|
+
port: number;
|
|
1206
|
+
/** Shared secret for authenticating requests */
|
|
1207
|
+
secret: string;
|
|
1208
|
+
/** Local Stalwart SMTP host (default: 127.0.0.1) */
|
|
1209
|
+
smtpHost?: string;
|
|
1210
|
+
/** Local Stalwart SMTP submission port (default: 587) */
|
|
1211
|
+
smtpPort?: number;
|
|
1212
|
+
/** Stalwart auth credentials for the sending agent */
|
|
1213
|
+
smtpUser: string;
|
|
1214
|
+
smtpPass: string;
|
|
1215
|
+
}
|
|
1216
|
+
/**
|
|
1217
|
+
* RelayBridge — A local HTTP-to-SMTP bridge that submits email to Stalwart.
|
|
1218
|
+
*
|
|
1219
|
+
* Stalwart then handles DKIM signing, MX resolution, and direct delivery
|
|
1220
|
+
* to the recipient's mail server on port 25. FROM is preserved exactly.
|
|
1221
|
+
*
|
|
1222
|
+
* This bridge is exposed via Cloudflare Tunnel so Cloudflare Workers
|
|
1223
|
+
* (which can't connect to port 25) can trigger outbound email through it.
|
|
1224
|
+
*
|
|
1225
|
+
* For production, deploy on a VPS with proper PTR/FCrDNS for reliable
|
|
1226
|
+
* delivery to all providers (Gmail, Outlook, etc.).
|
|
1227
|
+
*/
|
|
1228
|
+
declare class RelayBridge {
|
|
1229
|
+
private server;
|
|
1230
|
+
private options;
|
|
1231
|
+
constructor(options: RelayBridgeOptions);
|
|
1232
|
+
start(): Promise<void>;
|
|
1233
|
+
stop(): void;
|
|
1234
|
+
private handleRequest;
|
|
1235
|
+
private submitToStalwart;
|
|
1236
|
+
}
|
|
1237
|
+
declare function startRelayBridge(options: RelayBridgeOptions): RelayBridge;
|
|
1238
|
+
|
|
1239
|
+
declare function debug(tag: string, message: string): void;
|
|
1240
|
+
declare function debugWarn(tag: string, message: string): void;
|
|
1241
|
+
|
|
1242
|
+
interface DependencyStatus {
|
|
1243
|
+
name: string;
|
|
1244
|
+
installed: boolean;
|
|
1245
|
+
version?: string;
|
|
1246
|
+
description: string;
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* DependencyChecker detects which external tools are available.
|
|
1250
|
+
* All dependencies are required — setup will auto-install any missing ones.
|
|
1251
|
+
*/
|
|
1252
|
+
declare class DependencyChecker {
|
|
1253
|
+
checkAll(): Promise<DependencyStatus[]>;
|
|
1254
|
+
checkDocker(): Promise<DependencyStatus>;
|
|
1255
|
+
checkStalwart(): Promise<DependencyStatus>;
|
|
1256
|
+
checkCloudflared(): Promise<DependencyStatus>;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
type InstallProgress = (message: string) => void;
|
|
1260
|
+
/**
|
|
1261
|
+
* DependencyInstaller handles installing all external dependencies.
|
|
1262
|
+
* Everything is auto-installed — no optional deps.
|
|
1263
|
+
*/
|
|
1264
|
+
declare class DependencyInstaller {
|
|
1265
|
+
private onProgress;
|
|
1266
|
+
constructor(onProgress?: InstallProgress);
|
|
1267
|
+
/**
|
|
1268
|
+
* Install Docker if not present.
|
|
1269
|
+
* Uses Homebrew on macOS, apt on Linux.
|
|
1270
|
+
*/
|
|
1271
|
+
installDocker(): Promise<void>;
|
|
1272
|
+
/**
|
|
1273
|
+
* Wait for Docker daemon to be ready (up to 60s).
|
|
1274
|
+
*/
|
|
1275
|
+
private waitForDocker;
|
|
1276
|
+
/**
|
|
1277
|
+
* Start the Stalwart mail server Docker container.
|
|
1278
|
+
*/
|
|
1279
|
+
startStalwart(composePath: string): Promise<void>;
|
|
1280
|
+
/**
|
|
1281
|
+
* Download and install cloudflared to ~/.agenticmail/bin/cloudflared.
|
|
1282
|
+
* Returns the path to the installed binary.
|
|
1283
|
+
*/
|
|
1284
|
+
installCloudflared(): Promise<string>;
|
|
1285
|
+
/**
|
|
1286
|
+
* Install all dependencies. Checks each one and installs if missing.
|
|
1287
|
+
*/
|
|
1288
|
+
installAll(composePath: string): Promise<void>;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
interface SetupConfig {
|
|
1292
|
+
masterKey: string;
|
|
1293
|
+
stalwart: {
|
|
1294
|
+
url: string;
|
|
1295
|
+
adminUser: string;
|
|
1296
|
+
adminPassword: string;
|
|
1297
|
+
};
|
|
1298
|
+
smtp: {
|
|
1299
|
+
host: string;
|
|
1300
|
+
port: number;
|
|
1301
|
+
};
|
|
1302
|
+
imap: {
|
|
1303
|
+
host: string;
|
|
1304
|
+
port: number;
|
|
1305
|
+
};
|
|
1306
|
+
api: {
|
|
1307
|
+
port: number;
|
|
1308
|
+
host: string;
|
|
1309
|
+
};
|
|
1310
|
+
dataDir: string;
|
|
1311
|
+
}
|
|
1312
|
+
interface SetupResult {
|
|
1313
|
+
configPath: string;
|
|
1314
|
+
envPath: string;
|
|
1315
|
+
config: SetupConfig;
|
|
1316
|
+
isNew: boolean;
|
|
1317
|
+
}
|
|
1318
|
+
/**
|
|
1319
|
+
* SetupManager orchestrates AgenticMail initialization:
|
|
1320
|
+
* config generation, dependency installation, and service startup.
|
|
1321
|
+
* All dependencies are required and auto-installed.
|
|
1322
|
+
*/
|
|
1323
|
+
declare class SetupManager {
|
|
1324
|
+
private checker;
|
|
1325
|
+
private installer;
|
|
1326
|
+
constructor(onProgress?: InstallProgress);
|
|
1327
|
+
/**
|
|
1328
|
+
* Check all dependencies and return their status.
|
|
1329
|
+
*/
|
|
1330
|
+
checkDependencies(): Promise<DependencyStatus[]>;
|
|
1331
|
+
/**
|
|
1332
|
+
* Install all missing dependencies automatically.
|
|
1333
|
+
* Docker → Stalwart → cloudflared. Nothing is optional.
|
|
1334
|
+
*/
|
|
1335
|
+
installAll(composePath: string): Promise<void>;
|
|
1336
|
+
/**
|
|
1337
|
+
* Ensure a specific dependency is installed.
|
|
1338
|
+
*/
|
|
1339
|
+
ensureDocker(): Promise<void>;
|
|
1340
|
+
ensureStalwart(composePath?: string): Promise<void>;
|
|
1341
|
+
ensureCloudflared(): Promise<string>;
|
|
1342
|
+
/**
|
|
1343
|
+
* Get the path to docker-compose.yml.
|
|
1344
|
+
* Prefers ~/.agenticmail/docker-compose.yml (standalone),
|
|
1345
|
+
* falls back to monorepo location.
|
|
1346
|
+
*/
|
|
1347
|
+
getComposePath(): string;
|
|
1348
|
+
/**
|
|
1349
|
+
* Initialize AgenticMail config files.
|
|
1350
|
+
* If config already exists, returns the existing config without overwriting.
|
|
1351
|
+
* Always regenerates Docker files to keep passwords in sync.
|
|
1352
|
+
*/
|
|
1353
|
+
initConfig(): SetupResult;
|
|
1354
|
+
/**
|
|
1355
|
+
* Generate docker-compose.yml and stalwart.toml in ~/.agenticmail/
|
|
1356
|
+
* with the correct admin password from config.
|
|
1357
|
+
*/
|
|
1358
|
+
private generateDockerFiles;
|
|
1359
|
+
/**
|
|
1360
|
+
* Check if config has already been initialized.
|
|
1361
|
+
*/
|
|
1362
|
+
isInitialized(): boolean;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
export { AGENT_ROLES, AccountManager, type AddressInfo, type Agent, AgentDeletionService, type AgentRole, AgenticMailClient, type AgenticMailClientOptions, type AgenticMailConfig, type ArchiveAndDeleteOptions, type ArchivedEmail, type Attachment, type AttachmentAdvisory, CloudflareClient, type CreateAgentOptions, DEFAULT_AGENT_NAME, DEFAULT_AGENT_ROLE, DNSConfigurator, type DeletionReport, type DeletionSummary, DependencyChecker, DependencyInstaller, type DependencyStatus, type DnsRecord, type DnsSetupResult, type DomainInfo, DomainManager, type DomainModeConfig, type DomainPurchaseResult, DomainPurchaser, type DomainSearchResult, type DomainSetupResult, type EmailEnvelope, EmailSearchIndex, type FolderInfo, type GatewayConfig, GatewayManager, type GatewayManagerOptions, type GatewayMode, type GatewayStatus, type InboundEmail, type InboxEvent, type InboxExpungeEvent, type InboxFlagsEvent, type InboxNewEvent, InboxWatcher, type InboxWatcherOptions, type InstallProgress, type LinkAdvisory, type LocalSmtpConfig, MailReceiver, type MailReceiverOptions, MailSender, type MailSenderOptions, type MailboxInfo, type OutboundCategory, type OutboundScanInput, type OutboundScanResult, type OutboundWarning, type ParsedAttachment, type ParsedEmail, type PurchasedDomain, RELAY_PRESETS, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, SPAM_THRESHOLD, type SanitizeDetection, type SanitizeResult, type SearchCriteria, type SearchableEmail, type SecurityAdvisory, type SendMailOptions, type SendResult, type SendResultWithRaw, type SetupConfig, SetupManager, type SetupResult, type Severity, type SpamCategory, type SpamResult, type SpamRuleMatch, StalwartAdmin, type StalwartAdminOptions, type StalwartPrincipal, type TunnelConfig, TunnelManager, WARNING_THRESHOLD, type WatcherOptions, buildInboundSecurityAdvisory, closeDatabase, createTestDatabase, debug, debugWarn, ensureDataDir, getDatabase, isInternalEmail, parseEmail, resolveConfig, sanitizeEmail, saveConfig, scanOutboundEmail, scoreEmail, startRelayBridge };
|