@agenticmail/core 0.5.40 → 0.5.41

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.
@@ -0,0 +1,1664 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import Database, { Database as Database$1 } 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
+ sms?: {
185
+ enabled?: boolean;
186
+ phoneNumber?: string;
187
+ forwardingEmail?: string;
188
+ provider?: 'google_voice';
189
+ configuredAt?: string;
190
+ };
191
+ masterKey: string;
192
+ dataDir: string;
193
+ }
194
+ declare function resolveConfig(overrides?: Partial<AgenticMailConfig>): AgenticMailConfig;
195
+ declare function ensureDataDir(config: AgenticMailConfig): void;
196
+ declare function saveConfig(config: AgenticMailConfig): void;
197
+
198
+ interface StalwartPrincipal {
199
+ type: 'individual' | 'group' | 'domain' | 'list' | 'apiKey';
200
+ name: string;
201
+ secrets?: string[];
202
+ emails?: string[];
203
+ description?: string;
204
+ quota?: number;
205
+ memberOf?: string[];
206
+ members?: string[];
207
+ roles?: string[];
208
+ }
209
+
210
+ interface StalwartAdminOptions {
211
+ url: string;
212
+ adminUser: string;
213
+ adminPassword: string;
214
+ }
215
+ declare class StalwartAdmin {
216
+ private options;
217
+ private baseUrl;
218
+ private authHeader;
219
+ constructor(options: StalwartAdminOptions);
220
+ private request;
221
+ createPrincipal(principal: StalwartPrincipal): Promise<void>;
222
+ getPrincipal(name: string): Promise<StalwartPrincipal>;
223
+ updatePrincipal(name: string, changes: Partial<StalwartPrincipal>): Promise<void>;
224
+ /** Add an email alias to a principal without removing existing ones */
225
+ addEmailAlias(name: string, email: string): Promise<void>;
226
+ deletePrincipal(name: string): Promise<void>;
227
+ listPrincipals(type?: string): Promise<string[]>;
228
+ /** Ensure a domain exists in Stalwart (create if missing). */
229
+ ensureDomain(domain: string): Promise<void>;
230
+ healthCheck(): Promise<boolean>;
231
+ /** Get a Stalwart setting value. */
232
+ getSetting(key: string): Promise<string | undefined>;
233
+ /** Get all settings under a prefix. */
234
+ getSettings(prefix: string): Promise<Record<string, string>>;
235
+ /**
236
+ * Set a Stalwart configuration value via stalwart-cli.
237
+ * Note: stalwart-cli may return a 500 error even when the operation succeeds.
238
+ * We verify by listing the config afterwards.
239
+ */
240
+ private cliArgs;
241
+ updateSetting(key: string, value: string): Promise<void>;
242
+ /**
243
+ * Set the server hostname used in SMTP EHLO greetings.
244
+ * Critical for email deliverability — must match the sending domain.
245
+ */
246
+ setHostname(domain: string): Promise<void>;
247
+ /** Path to the host-side stalwart.toml (mounted read-only into container) */
248
+ private get configPath();
249
+ /** Path to host-side DKIM key directory */
250
+ private get dkimDir();
251
+ /**
252
+ * Create/reuse a DKIM signing key for a domain.
253
+ * Uses stalwart-cli to generate the key and store config in Stalwart's DB.
254
+ * Returns the public key (base64, no headers) for DNS TXT record.
255
+ */
256
+ createDkimSignature(domain: string, selector?: string): Promise<{
257
+ signatureId: string;
258
+ publicKey: string;
259
+ }>;
260
+ /**
261
+ * Restart the Stalwart Docker container and wait for it to be ready.
262
+ */
263
+ private restartContainer;
264
+ /**
265
+ * Check if a DKIM signature exists for a domain (checks Stalwart DB).
266
+ */
267
+ hasDkimSignature(domain: string): Promise<boolean>;
268
+ /**
269
+ * Configure Gmail SMTP as outbound relay (smarthost).
270
+ * Routes all non-local mail through smtp.gmail.com using app password auth.
271
+ * This bypasses the need for a PTR record on the sending IP.
272
+ */
273
+ configureOutboundRelay(config: {
274
+ smtpHost: string;
275
+ smtpPort: number;
276
+ username: string;
277
+ password: string;
278
+ routeName?: string;
279
+ }): Promise<void>;
280
+ }
281
+
282
+ /** Predefined agent roles */
283
+ type AgentRole = 'secretary' | 'assistant' | 'researcher' | 'writer' | 'custom';
284
+ declare const AGENT_ROLES: readonly AgentRole[];
285
+ declare const DEFAULT_AGENT_ROLE: AgentRole;
286
+ declare const DEFAULT_AGENT_NAME = "secretary";
287
+ interface Agent {
288
+ id: string;
289
+ name: string;
290
+ email: string;
291
+ apiKey: string;
292
+ stalwartPrincipal: string;
293
+ createdAt: string;
294
+ updatedAt: string;
295
+ metadata: Record<string, unknown>;
296
+ role: AgentRole;
297
+ }
298
+ interface CreateAgentOptions {
299
+ name: string;
300
+ domain?: string;
301
+ password?: string;
302
+ metadata?: Record<string, unknown>;
303
+ gateway?: 'relay' | 'domain';
304
+ role?: AgentRole;
305
+ }
306
+
307
+ declare class AccountManager {
308
+ private db;
309
+ private stalwart;
310
+ constructor(db: Database.Database, stalwart: StalwartAdmin);
311
+ create(options: CreateAgentOptions): Promise<Agent>;
312
+ getById(id: string): Promise<Agent | null>;
313
+ getByApiKey(apiKey: string): Promise<Agent | null>;
314
+ getByName(name: string): Promise<Agent | null>;
315
+ list(): Promise<Agent[]>;
316
+ getByRole(role: AgentRole): Promise<Agent[]>;
317
+ delete(id: string): Promise<boolean>;
318
+ updateMetadata(id: string, metadata: Record<string, unknown>): Promise<Agent | null>;
319
+ getCredentials(id: string): Promise<{
320
+ email: string;
321
+ password: string;
322
+ principal: string;
323
+ smtpHost: string;
324
+ smtpPort: number;
325
+ imapHost: string;
326
+ imapPort: number;
327
+ } | null>;
328
+ }
329
+
330
+ interface ArchivedEmail {
331
+ uid: number;
332
+ messageId: string;
333
+ from: string;
334
+ to: string[];
335
+ subject: string;
336
+ date: string;
337
+ text?: string;
338
+ html?: string;
339
+ }
340
+ interface DeletionReport {
341
+ id: string;
342
+ agent: {
343
+ id: string;
344
+ name: string;
345
+ email: string;
346
+ role: string;
347
+ createdAt: string;
348
+ };
349
+ deletedAt: string;
350
+ deletedBy: string;
351
+ reason?: string;
352
+ emails: {
353
+ inbox: ArchivedEmail[];
354
+ sent: ArchivedEmail[];
355
+ other: Record<string, ArchivedEmail[]>;
356
+ };
357
+ summary: {
358
+ totalEmails: number;
359
+ inboxCount: number;
360
+ sentCount: number;
361
+ otherCount: number;
362
+ folders: string[];
363
+ firstEmailDate?: string;
364
+ lastEmailDate?: string;
365
+ topCorrespondents: {
366
+ address: string;
367
+ count: number;
368
+ }[];
369
+ };
370
+ }
371
+ interface DeletionSummary {
372
+ id: string;
373
+ agentName: string;
374
+ agentEmail: string;
375
+ agentRole: string | null;
376
+ deletedAt: string;
377
+ deletedBy: string | null;
378
+ reason: string | null;
379
+ emailCount: number;
380
+ filePath: string | null;
381
+ }
382
+ interface ArchiveAndDeleteOptions {
383
+ deletedBy?: string;
384
+ reason?: string;
385
+ }
386
+ declare class AgentDeletionService {
387
+ private db;
388
+ private accountManager;
389
+ private config;
390
+ constructor(db: Database.Database, accountManager: AccountManager, config: AgenticMailConfig);
391
+ archiveAndDelete(agentId: string, options?: ArchiveAndDeleteOptions): Promise<DeletionReport>;
392
+ getReport(deletionId: string): DeletionReport | null;
393
+ listReports(): DeletionSummary[];
394
+ private archiveEmails;
395
+ private archiveFolder;
396
+ private buildSummary;
397
+ private saveToFile;
398
+ private saveToDatabase;
399
+ }
400
+
401
+ interface MailSenderOptions {
402
+ host: string;
403
+ port: number;
404
+ email: string;
405
+ password: string;
406
+ authUser?: string;
407
+ secure?: boolean;
408
+ }
409
+ interface SendResultWithRaw extends SendResult {
410
+ /** Raw RFC822 message bytes (for appending to Sent folder) */
411
+ raw: Buffer;
412
+ }
413
+ declare class MailSender {
414
+ private options;
415
+ private transporter;
416
+ private email;
417
+ constructor(options: MailSenderOptions);
418
+ send(mail: SendMailOptions): Promise<SendResultWithRaw>;
419
+ verify(): Promise<boolean>;
420
+ close(): void;
421
+ }
422
+
423
+ interface MailReceiverOptions {
424
+ host: string;
425
+ port: number;
426
+ email: string;
427
+ password: string;
428
+ secure?: boolean;
429
+ }
430
+ declare class MailReceiver {
431
+ private options;
432
+ private client;
433
+ private connected;
434
+ constructor(options: MailReceiverOptions);
435
+ connect(): Promise<void>;
436
+ /** Check if the IMAP client is actually usable */
437
+ get usable(): boolean;
438
+ disconnect(): Promise<void>;
439
+ getMailboxInfo(mailbox?: string): Promise<MailboxInfo>;
440
+ listEnvelopes(mailbox?: string, options?: {
441
+ limit?: number;
442
+ offset?: number;
443
+ }): Promise<EmailEnvelope[]>;
444
+ fetchMessage(uid: number, mailbox?: string): Promise<Buffer>;
445
+ search(criteria: SearchCriteria, mailbox?: string): Promise<number[]>;
446
+ markSeen(uid: number, mailbox?: string): Promise<void>;
447
+ deleteMessage(uid: number, mailbox?: string): Promise<void>;
448
+ /** Mark a message as unseen (unread) */
449
+ markUnseen(uid: number, mailbox?: string): Promise<void>;
450
+ /** Move a message to another folder */
451
+ moveMessage(uid: number, fromMailbox: string, toMailbox: string): Promise<void>;
452
+ /** List all IMAP folders/mailboxes */
453
+ listFolders(): Promise<FolderInfo[]>;
454
+ /** Create a new IMAP folder */
455
+ createFolder(path: string): Promise<void>;
456
+ /** Batch mark multiple messages as seen */
457
+ batchMarkSeen(uids: number[], mailbox?: string): Promise<void>;
458
+ /** Batch mark multiple messages as unseen */
459
+ batchMarkUnseen(uids: number[], mailbox?: string): Promise<void>;
460
+ /** Batch delete multiple messages */
461
+ batchDelete(uids: number[], mailbox?: string): Promise<void>;
462
+ /** Batch fetch raw message content for multiple UIDs */
463
+ batchFetch(uids: number[], mailbox?: string): Promise<Map<number, Buffer>>;
464
+ /** Batch move multiple messages to another folder */
465
+ batchMove(uids: number[], fromMailbox: string, toMailbox: string): Promise<void>;
466
+ /** Append a raw RFC822 message to a mailbox (e.g. "Sent") with given flags */
467
+ appendMessage(raw: Buffer, mailbox: string, flags?: string[]): Promise<void>;
468
+ getImapClient(): ImapFlow;
469
+ }
470
+ interface FolderInfo {
471
+ path: string;
472
+ name: string;
473
+ specialUse?: string;
474
+ flags: string[];
475
+ }
476
+
477
+ declare function parseEmail(raw: Buffer | string): Promise<ParsedEmail>;
478
+
479
+ type SpamCategory = 'prompt_injection' | 'social_engineering' | 'data_exfiltration' | 'phishing' | 'header_anomaly' | 'content_spam' | 'link_analysis' | 'authentication' | 'attachment_risk';
480
+ interface SpamRuleMatch {
481
+ ruleId: string;
482
+ category: SpamCategory;
483
+ score: number;
484
+ description: string;
485
+ }
486
+ interface SpamResult {
487
+ score: number;
488
+ isSpam: boolean;
489
+ isWarning: boolean;
490
+ matches: SpamRuleMatch[];
491
+ topCategory: SpamCategory | null;
492
+ }
493
+ declare const SPAM_THRESHOLD = 40;
494
+ declare const WARNING_THRESHOLD = 20;
495
+ declare function isInternalEmail(email: ParsedEmail, localDomains?: string[]): boolean;
496
+ declare function scoreEmail(email: ParsedEmail): SpamResult;
497
+
498
+ interface SanitizeDetection {
499
+ type: string;
500
+ description: string;
501
+ count: number;
502
+ }
503
+ interface SanitizeResult {
504
+ text: string;
505
+ html: string;
506
+ detections: SanitizeDetection[];
507
+ wasModified: boolean;
508
+ }
509
+ declare function sanitizeEmail(email: ParsedEmail): SanitizeResult;
510
+
511
+ /**
512
+ * Outbound Email Guard — scans outgoing emails for sensitive content.
513
+ * Pure functions, zero external dependencies.
514
+ */
515
+ type OutboundCategory = 'pii' | 'credential' | 'system_internal' | 'owner_privacy' | 'attachment_risk';
516
+ type Severity = 'high' | 'medium';
517
+ interface OutboundWarning {
518
+ category: OutboundCategory;
519
+ severity: Severity;
520
+ ruleId: string;
521
+ description: string;
522
+ match: string;
523
+ }
524
+ interface OutboundScanResult {
525
+ warnings: OutboundWarning[];
526
+ hasHighSeverity: boolean;
527
+ hasMediumSeverity: boolean;
528
+ blocked: boolean;
529
+ summary: string;
530
+ }
531
+ interface OutboundScanInput {
532
+ to: string | string[];
533
+ subject?: string;
534
+ text?: string;
535
+ html?: string;
536
+ attachments?: Array<{
537
+ filename?: string;
538
+ contentType?: string;
539
+ content?: string | Buffer;
540
+ encoding?: string;
541
+ }>;
542
+ }
543
+ interface AttachmentAdvisory {
544
+ filename: string;
545
+ risk: string;
546
+ detail: string;
547
+ }
548
+ interface LinkAdvisory {
549
+ ruleId: string;
550
+ detail: string;
551
+ }
552
+ interface SecurityAdvisory {
553
+ spamScore?: number;
554
+ spamCategory?: string;
555
+ isSpam?: boolean;
556
+ isWarning?: boolean;
557
+ attachmentWarnings: AttachmentAdvisory[];
558
+ linkWarnings: LinkAdvisory[];
559
+ summary: string;
560
+ }
561
+ /**
562
+ * Scans outgoing email content for sensitive data (PII, credentials, system info, owner privacy).
563
+ * Skips scanning entirely if ALL recipients are @localhost (internal agent-to-agent communication).
564
+ */
565
+ declare function scanOutboundEmail(input: OutboundScanInput): OutboundScanResult;
566
+ /**
567
+ * Builds a structured security advisory from email metadata (spam score, attachments, link warnings).
568
+ * Used by tool handlers to present per-attachment and per-link warnings to the agent.
569
+ */
570
+ declare function buildInboundSecurityAdvisory(security: {
571
+ score?: number;
572
+ category?: string;
573
+ isSpam?: boolean;
574
+ isWarning?: boolean;
575
+ matches?: Array<{
576
+ ruleId: string;
577
+ }>;
578
+ } | undefined, attachments: Array<{
579
+ filename?: string;
580
+ contentType?: string;
581
+ size?: number;
582
+ }> | undefined): SecurityAdvisory;
583
+
584
+ declare function getDatabase(config: AgenticMailConfig): Database.Database;
585
+ declare function closeDatabase(): void;
586
+ declare function createTestDatabase(): Database.Database;
587
+
588
+ interface SearchableEmail {
589
+ agentId: string;
590
+ messageId: string;
591
+ subject: string;
592
+ fromAddress: string;
593
+ toAddress: string;
594
+ bodyText: string;
595
+ receivedAt: string;
596
+ }
597
+ declare class EmailSearchIndex {
598
+ private db;
599
+ constructor(db: Database.Database);
600
+ index(email: SearchableEmail): void;
601
+ search(agentId: string, query: string, limit?: number): SearchableEmail[];
602
+ deleteByAgent(agentId: string): void;
603
+ }
604
+
605
+ interface DomainInfo {
606
+ domain: string;
607
+ stalwartPrincipal: string;
608
+ dkimSelector?: string;
609
+ dkimPublicKey?: string;
610
+ verified: boolean;
611
+ createdAt: string;
612
+ }
613
+ interface DnsRecord {
614
+ type: 'TXT' | 'CNAME' | 'MX';
615
+ name: string;
616
+ value: string;
617
+ purpose: string;
618
+ }
619
+ interface DomainSetupResult {
620
+ domain: string;
621
+ dnsRecords: DnsRecord[];
622
+ }
623
+
624
+ declare class DomainManager {
625
+ private db;
626
+ private stalwart;
627
+ constructor(db: Database.Database, stalwart: StalwartAdmin);
628
+ setup(domain: string): Promise<DomainSetupResult>;
629
+ private generateDnsRecords;
630
+ get(domain: string): Promise<DomainInfo | null>;
631
+ list(): Promise<DomainInfo[]>;
632
+ getDnsRecords(domain: string): Promise<DnsRecord[]>;
633
+ verify(domain: string): Promise<boolean>;
634
+ delete(domain: string): Promise<boolean>;
635
+ }
636
+
637
+ type GatewayMode = 'relay' | 'domain' | 'none';
638
+ type RelayProvider = 'gmail' | 'outlook' | 'custom';
639
+ interface RelayConfig {
640
+ provider: RelayProvider;
641
+ email: string;
642
+ password: string;
643
+ smtpHost: string;
644
+ smtpPort: number;
645
+ imapHost: string;
646
+ imapPort: number;
647
+ }
648
+ interface DomainModeConfig {
649
+ domain: string;
650
+ cloudflareApiToken: string;
651
+ cloudflareAccountId: string;
652
+ tunnelId?: string;
653
+ tunnelToken?: string;
654
+ /** URL of the Cloudflare Worker outbound relay (e.g. https://agenticmail-outbound.xxx.workers.dev) */
655
+ outboundWorkerUrl?: string;
656
+ /** Shared secret for authenticating with the outbound Worker */
657
+ outboundSecret?: string;
658
+ /** Shared secret for authenticating inbound webhook from Email Worker */
659
+ inboundSecret?: string;
660
+ /** Name of the deployed Email Worker */
661
+ emailWorkerName?: string;
662
+ }
663
+ interface GatewayConfig {
664
+ mode: GatewayMode;
665
+ relay?: RelayConfig;
666
+ domain?: DomainModeConfig;
667
+ }
668
+ interface GatewayStatus {
669
+ mode: GatewayMode;
670
+ healthy: boolean;
671
+ relay?: {
672
+ provider: RelayProvider;
673
+ email: string;
674
+ polling: boolean;
675
+ };
676
+ domain?: {
677
+ domain: string;
678
+ dnsConfigured: boolean;
679
+ tunnelActive: boolean;
680
+ };
681
+ }
682
+ interface PurchasedDomain {
683
+ domain: string;
684
+ registrar: string;
685
+ cloudflareZoneId?: string;
686
+ tunnelId?: string;
687
+ dnsConfigured: boolean;
688
+ tunnelActive: boolean;
689
+ purchasedAt: string;
690
+ }
691
+ /** Relay provider presets for SMTP/IMAP connection details */
692
+ declare const RELAY_PRESETS: Record<'gmail' | 'outlook', Pick<RelayConfig, 'smtpHost' | 'smtpPort' | 'imapHost' | 'imapPort'>>;
693
+ interface CloudflareZone {
694
+ id: string;
695
+ name: string;
696
+ status: string;
697
+ name_servers: string[];
698
+ }
699
+ interface CloudflareDnsRecord {
700
+ id: string;
701
+ type: string;
702
+ name: string;
703
+ content: string;
704
+ ttl: number;
705
+ proxied?: boolean;
706
+ priority?: number;
707
+ }
708
+ interface CloudflareTunnel {
709
+ id: string;
710
+ name: string;
711
+ status: string;
712
+ created_at: string;
713
+ deleted_at?: string | null;
714
+ connections: Array<{
715
+ id: string;
716
+ }>;
717
+ }
718
+ interface CloudflareDomainAvailability {
719
+ name: string;
720
+ available: boolean;
721
+ premium: boolean;
722
+ price?: number;
723
+ }
724
+
725
+ interface RelayGatewayOptions {
726
+ onInboundMail?: (agentName: string, parsed: InboundEmail) => void | Promise<void>;
727
+ /** Fallback agent name for emails without sub-addressing */
728
+ defaultAgentName?: string;
729
+ }
730
+ interface InboundEmail {
731
+ messageId: string;
732
+ from: string;
733
+ to: string;
734
+ subject: string;
735
+ text?: string;
736
+ html?: string;
737
+ date: Date;
738
+ inReplyTo?: string;
739
+ references?: string[];
740
+ attachments?: Array<{
741
+ filename: string;
742
+ contentType: string;
743
+ size: number;
744
+ content: Buffer;
745
+ }>;
746
+ }
747
+ /**
748
+ * RelayGateway handles sending/receiving email through an existing
749
+ * Gmail/Outlook account using sub-addressing (user+agent@gmail.com).
750
+ */
751
+ declare class RelayGateway {
752
+ private smtpTransport;
753
+ private pollTimer;
754
+ private polling;
755
+ private config;
756
+ private onInboundMail;
757
+ private defaultAgentName;
758
+ private _pollInProgress;
759
+ /** Track highest UID seen so we only process new messages after first poll */
760
+ private lastSeenUid;
761
+ private firstPollDone;
762
+ /** Callback invoked when lastSeenUid advances (for persistence) */
763
+ onUidAdvance: ((uid: number) => void) | null;
764
+ /** Map sent messageId → agentName for In-Reply-To based routing */
765
+ private sentMessageIds;
766
+ /** Robustness: consecutive failure tracking + backoff */
767
+ private consecutiveFailures;
768
+ private pollIntervalMs;
769
+ private readonly MAX_BACKOFF_MS;
770
+ private readonly CONNECT_TIMEOUT_MS;
771
+ constructor(options?: RelayGatewayOptions);
772
+ setup(config: RelayConfig): Promise<void>;
773
+ /**
774
+ * Send an email through the relay SMTP server.
775
+ * Rewrites the From address to use sub-addressing: relay+agentName@gmail.com
776
+ */
777
+ sendViaRelay(agentName: string, mail: SendMailOptions): Promise<SendResultWithRaw>;
778
+ /**
779
+ * Start polling the relay IMAP account for new emails.
780
+ * Routes inbound mail to the correct agent based on sub-addressing or In-Reply-To.
781
+ *
782
+ * Robust design:
783
+ * - Uses setTimeout (not setInterval) so backoff works naturally
784
+ * - Exponential backoff on consecutive failures (30s → 1m → 2m → 5m cap)
785
+ * - Always reschedules — polling never permanently stops
786
+ * - Connection timeout prevents hung connections
787
+ * - Detailed failure logging with recovery info
788
+ */
789
+ startPolling(intervalMs?: number): Promise<void>;
790
+ stopPolling(): void;
791
+ /** Calculate next poll delay with exponential backoff on failures */
792
+ private getNextPollDelay;
793
+ /** Schedule the next poll with appropriate delay */
794
+ private scheduleNextPoll;
795
+ private pollOnce;
796
+ private _doPoll;
797
+ /**
798
+ * Check if an email address is one of our relay sender addresses (user+agent@domain).
799
+ */
800
+ private isOurRelaySender;
801
+ /**
802
+ * Extract agent name from email using multiple strategies:
803
+ * 1. Sub-address in To/CC/Delivered-To/X-Original-To headers
804
+ * 2. In-Reply-To matching against sent message IDs
805
+ * 3. References chain matching against sent message IDs
806
+ */
807
+ private extractAgentName;
808
+ /**
809
+ * Register a sent message ID for reply tracking.
810
+ * Called externally when messages are sent via relay from GatewayManager.
811
+ */
812
+ trackSentMessage(messageId: string, agentName: string): void;
813
+ /**
814
+ * Restore lastSeenUid from persistent storage (used on resume after restart).
815
+ * If the restored UID is > 0, also marks firstPollDone to skip the initial window scan.
816
+ */
817
+ setLastSeenUid(uid: number): void;
818
+ isConfigured(): boolean;
819
+ isPolling(): boolean;
820
+ getConfig(): RelayConfig | null;
821
+ /**
822
+ * Search the relay IMAP account (Gmail/Outlook) for emails matching criteria.
823
+ * Returns parsed envelope data so results can be merged with local search.
824
+ */
825
+ searchRelay(criteria: {
826
+ from?: string;
827
+ to?: string;
828
+ subject?: string;
829
+ text?: string;
830
+ since?: Date;
831
+ before?: Date;
832
+ seen?: boolean;
833
+ }, maxResults?: number): Promise<RelaySearchResult[]>;
834
+ /**
835
+ * Fetch a full email from the relay account by UID and return it as an InboundEmail.
836
+ * Used to import a specific email from Gmail/Outlook into the local inbox.
837
+ */
838
+ fetchRelayMessage(uid: number): Promise<InboundEmail | null>;
839
+ shutdown(): Promise<void>;
840
+ }
841
+ interface RelaySearchResult {
842
+ uid: number;
843
+ source: 'relay';
844
+ account: string;
845
+ messageId: string;
846
+ subject: string;
847
+ from: Array<{
848
+ name?: string;
849
+ address: string;
850
+ }>;
851
+ to: Array<{
852
+ name?: string;
853
+ address: string;
854
+ }>;
855
+ date: Date;
856
+ flags: string[];
857
+ }
858
+
859
+ declare class CloudflareClient {
860
+ private token;
861
+ private accountId;
862
+ constructor(token: string, accountId: string);
863
+ listZones(): Promise<CloudflareZone[]>;
864
+ getZone(domain: string): Promise<CloudflareZone | null>;
865
+ createZone(domain: string): Promise<CloudflareZone>;
866
+ listDnsRecords(zoneId: string): Promise<CloudflareDnsRecord[]>;
867
+ createDnsRecord(zoneId: string, record: {
868
+ type: string;
869
+ name: string;
870
+ content: string;
871
+ ttl?: number;
872
+ priority?: number;
873
+ proxied?: boolean;
874
+ }): Promise<CloudflareDnsRecord>;
875
+ deleteDnsRecord(zoneId: string, recordId: string): Promise<void>;
876
+ searchDomains(query: string): Promise<CloudflareDomainAvailability[]>;
877
+ checkAvailability(domain: string): Promise<CloudflareDomainAvailability>;
878
+ purchaseDomain(domain: string, autoRenew?: boolean): Promise<{
879
+ domain: string;
880
+ status: string;
881
+ }>;
882
+ listRegisteredDomains(): Promise<Array<{
883
+ domain: string;
884
+ status: string;
885
+ }>>;
886
+ createTunnel(name: string): Promise<CloudflareTunnel>;
887
+ getTunnel(tunnelId: string): Promise<CloudflareTunnel>;
888
+ getTunnelToken(tunnelId: string): Promise<string>;
889
+ createTunnelRoute(tunnelId: string, hostname: string, service: string, options?: {
890
+ apiService?: string;
891
+ }): Promise<void>;
892
+ deleteTunnel(tunnelId: string): Promise<void>;
893
+ /** Enable Email Routing on a zone */
894
+ enableEmailRouting(zoneId: string): Promise<void>;
895
+ /** Disable Email Routing on a zone (unlocks managed MX/SPF records for deletion) */
896
+ disableEmailRouting(zoneId: string): Promise<void>;
897
+ /** Get Email Routing status for a zone */
898
+ getEmailRoutingStatus(zoneId: string): Promise<{
899
+ enabled: boolean;
900
+ status: string;
901
+ }>;
902
+ /** Set catch-all rule to forward to a Worker */
903
+ setCatchAllWorkerRule(zoneId: string, workerName: string): Promise<void>;
904
+ /** Deploy an Email Worker script (ES module format) */
905
+ deployEmailWorker(scriptName: string, scriptContent: string, envVars?: Record<string, string>): Promise<void>;
906
+ /** Delete a Worker script */
907
+ deleteWorker(scriptName: string): Promise<void>;
908
+ listTunnels(): Promise<CloudflareTunnel[]>;
909
+ private request;
910
+ }
911
+
912
+ interface DomainSearchResult {
913
+ domain: string;
914
+ available: boolean;
915
+ premium: boolean;
916
+ price?: number;
917
+ }
918
+ interface DomainPurchaseResult {
919
+ domain: string;
920
+ status: string;
921
+ }
922
+ /**
923
+ * DomainPurchaser handles searching for and purchasing domains
924
+ * via Cloudflare Registrar.
925
+ */
926
+ declare class DomainPurchaser {
927
+ private cf;
928
+ constructor(cf: CloudflareClient);
929
+ /**
930
+ * Search for available domains matching the given keywords.
931
+ * Appends common TLDs to keywords and checks availability.
932
+ */
933
+ searchAvailable(keywords: string[], tlds?: string[]): Promise<DomainSearchResult[]>;
934
+ /**
935
+ * Purchase a domain via Cloudflare Registrar.
936
+ * NOTE: Cloudflare API tokens only support READ access for registrar.
937
+ * Domain purchases must be done manually via the Cloudflare dashboard
938
+ * or another registrar (then point nameservers to Cloudflare).
939
+ */
940
+ purchase(_domain: string, _autoRenew?: boolean): Promise<DomainPurchaseResult>;
941
+ /**
942
+ * Check the registration status of a purchased domain.
943
+ */
944
+ getStatus(domain: string): Promise<{
945
+ domain: string;
946
+ status: string;
947
+ }>;
948
+ /**
949
+ * List all domains registered under the Cloudflare account.
950
+ */
951
+ listRegistered(): Promise<Array<{
952
+ domain: string;
953
+ status: string;
954
+ }>>;
955
+ }
956
+
957
+ interface DnsSetupResult {
958
+ records: Array<{
959
+ type: string;
960
+ name: string;
961
+ content: string;
962
+ purpose: string;
963
+ }>;
964
+ removed: Array<{
965
+ type: string;
966
+ name: string;
967
+ content: string;
968
+ reason: string;
969
+ }>;
970
+ }
971
+ /**
972
+ * DNSConfigurator automatically creates MX, SPF, DKIM, and DMARC
973
+ * DNS records for a domain using the Cloudflare API.
974
+ * Replaces conflicting records (old MX, SPF, A records) to ensure clean setup.
975
+ */
976
+ declare class DNSConfigurator {
977
+ private cf;
978
+ constructor(cf: CloudflareClient);
979
+ /**
980
+ * Configure all DNS records required for email on a domain.
981
+ * Replaces existing MX and SPF records that conflict with AgenticMail.
982
+ */
983
+ /**
984
+ * Detect the server's public IPv4 address for SPF records.
985
+ */
986
+ detectPublicIp(): Promise<string | null>;
987
+ configureForEmail(domain: string, zoneId: string, options?: {
988
+ dkimSelector?: string;
989
+ dkimPublicKey?: string;
990
+ serverIp?: string;
991
+ }): Promise<DnsSetupResult>;
992
+ /**
993
+ * Configure DNS records to point the domain at a Cloudflare Tunnel.
994
+ * Removes conflicting A/AAAA records for the root domain first.
995
+ */
996
+ configureForTunnel(domain: string, zoneId: string, tunnelId: string): Promise<DnsSetupResult['removed']>;
997
+ /**
998
+ * Verify DNS propagation by resolving MX and TXT records.
999
+ */
1000
+ verify(domain: string): Promise<{
1001
+ mx: boolean;
1002
+ spf: boolean;
1003
+ dmarc: boolean;
1004
+ }>;
1005
+ }
1006
+
1007
+ interface TunnelConfig {
1008
+ tunnelId: string;
1009
+ tunnelToken: string;
1010
+ }
1011
+ /**
1012
+ * TunnelManager handles the Cloudflare Tunnel lifecycle:
1013
+ * downloading cloudflared, creating/starting tunnels, and routing ingress.
1014
+ */
1015
+ declare class TunnelManager {
1016
+ private cf;
1017
+ private process;
1018
+ private running;
1019
+ private binPath;
1020
+ constructor(cf: CloudflareClient);
1021
+ /**
1022
+ * Find or download the cloudflared binary.
1023
+ * Checks: managed binary → system-wide → download.
1024
+ */
1025
+ install(): Promise<string>;
1026
+ /**
1027
+ * Create a new Cloudflare Tunnel via the API.
1028
+ * If a tunnel with the same name already exists, reuse it.
1029
+ */
1030
+ create(name: string): Promise<TunnelConfig>;
1031
+ /**
1032
+ * Start the cloudflared tunnel process.
1033
+ */
1034
+ start(tunnelToken: string): Promise<void>;
1035
+ /**
1036
+ * Configure tunnel ingress rules: route mail.{domain} → SMTP, {domain} → HTTP
1037
+ */
1038
+ createIngress(tunnelId: string, domain: string, smtpPort?: number, httpPort?: number, apiPort?: number): Promise<void>;
1039
+ /**
1040
+ * Stop the running cloudflared process.
1041
+ */
1042
+ stop(): Promise<void>;
1043
+ /**
1044
+ * Check if the tunnel is currently running.
1045
+ */
1046
+ status(): {
1047
+ running: boolean;
1048
+ pid?: number;
1049
+ };
1050
+ /**
1051
+ * Check tunnel health via the Cloudflare API.
1052
+ */
1053
+ healthCheck(tunnelId: string): Promise<{
1054
+ healthy: boolean;
1055
+ status: string;
1056
+ }>;
1057
+ }
1058
+
1059
+ interface LocalSmtpConfig {
1060
+ host: string;
1061
+ port: number;
1062
+ user: string;
1063
+ pass: string;
1064
+ }
1065
+ interface GatewayManagerOptions {
1066
+ db: Database.Database;
1067
+ stalwart: StalwartAdmin;
1068
+ accountManager?: AccountManager;
1069
+ localSmtp?: LocalSmtpConfig;
1070
+ onInboundMail?: (agentName: string, mail: InboundEmail) => void | Promise<void>;
1071
+ /** Master key used to encrypt credentials at rest in SQLite. */
1072
+ encryptionKey?: string;
1073
+ }
1074
+ /**
1075
+ * GatewayManager orchestrates relay and domain modes for sending/receiving
1076
+ * real internet email. It coordinates between the relay gateway, Cloudflare
1077
+ * services (DNS, tunnels, registrar), and the local Stalwart instance.
1078
+ */
1079
+ declare class GatewayManager {
1080
+ private options;
1081
+ private db;
1082
+ private stalwart;
1083
+ private accountManager;
1084
+ private relay;
1085
+ private config;
1086
+ private cfClient;
1087
+ private tunnel;
1088
+ private dnsConfigurator;
1089
+ private domainPurchaser;
1090
+ private smsManager;
1091
+ private smsPollers;
1092
+ private encryptionKey;
1093
+ constructor(options: GatewayManagerOptions);
1094
+ /**
1095
+ * Check if a message has already been delivered to an agent (deduplication).
1096
+ */
1097
+ isAlreadyDelivered(messageId: string, agentName: string): boolean;
1098
+ /**
1099
+ * Record that a message was delivered to an agent.
1100
+ */
1101
+ recordDelivery(messageId: string, agentName: string): void;
1102
+ /**
1103
+ * Built-in inbound mail handler: delivers relay inbound mail to agent's local Stalwart mailbox.
1104
+ * Authenticates as the agent to send to their own mailbox (Stalwart requires sender = auth user).
1105
+ *
1106
+ * Also intercepts owner replies to approval notification emails — if the reply says
1107
+ * "approve" or "reject", the pending outbound email is automatically processed.
1108
+ */
1109
+ private deliverInboundLocally;
1110
+ /**
1111
+ * Check if an inbound email is a reply to a pending approval notification.
1112
+ * If the reply body starts with "approve"/"yes" or "reject"/"no", automatically
1113
+ * process the pending email (send it or discard it) and confirm to the owner.
1114
+ */
1115
+ private tryProcessApprovalReply;
1116
+ /**
1117
+ * Execute approval of a pending outbound email: look up the agent, reconstitute
1118
+ * attachments, and send the email via gateway routing or local SMTP.
1119
+ */
1120
+ private executeApproval;
1121
+ /**
1122
+ * Send a confirmation email back to the owner after processing an approval reply.
1123
+ */
1124
+ private sendApprovalConfirmation;
1125
+ setupRelay(config: RelayConfig, options?: {
1126
+ defaultAgentName?: string;
1127
+ defaultAgentRole?: AgentRole;
1128
+ skipDefaultAgent?: boolean;
1129
+ }): Promise<{
1130
+ agent?: Agent;
1131
+ }>;
1132
+ setupDomain(options: {
1133
+ cloudflareToken: string;
1134
+ cloudflareAccountId: string;
1135
+ domain?: string;
1136
+ purchase?: {
1137
+ keywords: string[];
1138
+ tld?: string;
1139
+ };
1140
+ outboundWorkerUrl?: string;
1141
+ outboundSecret?: string;
1142
+ gmailRelay?: {
1143
+ email: string;
1144
+ appPassword: string;
1145
+ };
1146
+ }): Promise<{
1147
+ domain: string;
1148
+ dnsConfigured: boolean;
1149
+ tunnelId: string;
1150
+ outboundRelay?: {
1151
+ configured: boolean;
1152
+ provider: string;
1153
+ };
1154
+ nextSteps?: string[];
1155
+ }>;
1156
+ /**
1157
+ * Send a test email through the gateway without requiring a real agent.
1158
+ * In relay mode, uses "test" as the sub-address.
1159
+ * In domain mode, uses the first available agent (Stalwart needs real credentials).
1160
+ */
1161
+ sendTestEmail(to: string): Promise<SendResultWithRaw | null>;
1162
+ /**
1163
+ * Route an outbound email. If the destination is external and a gateway
1164
+ * is configured, send via the appropriate channel.
1165
+ * Returns null if the mail should be sent via local Stalwart.
1166
+ */
1167
+ routeOutbound(agentName: string, mail: SendMailOptions): Promise<SendResultWithRaw | null>;
1168
+ /**
1169
+ * Send email by submitting to local Stalwart via SMTP (port 587).
1170
+ * Stalwart handles DKIM signing and delivery (direct or via relay).
1171
+ * Reply-To is set to the agent's domain email so replies come back
1172
+ * to the domain (handled by Cloudflare Email Routing → inbound Worker).
1173
+ */
1174
+ private sendViaStalwart;
1175
+ getStatus(): GatewayStatus;
1176
+ getMode(): GatewayMode;
1177
+ getConfig(): GatewayConfig;
1178
+ getStalwart(): StalwartAdmin;
1179
+ getDomainPurchaser(): DomainPurchaser | null;
1180
+ getDNSConfigurator(): DNSConfigurator | null;
1181
+ getTunnelManager(): TunnelManager | null;
1182
+ getRelay(): RelayGateway;
1183
+ /**
1184
+ * Search the connected relay account (Gmail/Outlook) for emails matching criteria.
1185
+ * Returns empty array if relay is not configured.
1186
+ */
1187
+ searchRelay(criteria: {
1188
+ from?: string;
1189
+ to?: string;
1190
+ subject?: string;
1191
+ text?: string;
1192
+ since?: Date;
1193
+ before?: Date;
1194
+ seen?: boolean;
1195
+ }, maxResults?: number): Promise<RelaySearchResult[]>;
1196
+ /**
1197
+ * Import an email from the connected relay account into an agent's local inbox.
1198
+ * Fetches the full message from relay IMAP and delivers it locally, preserving
1199
+ * all headers (Message-ID, In-Reply-To, References) for thread continuity.
1200
+ */
1201
+ importRelayMessage(relayUid: number, agentName: string): Promise<{
1202
+ success: boolean;
1203
+ error?: string;
1204
+ }>;
1205
+ /**
1206
+ * Start SMS pollers for all agents that have separate GV Gmail credentials.
1207
+ * Agents with sameAsRelay=true are handled in deliverInboundLocally.
1208
+ */
1209
+ private startSmsPollers;
1210
+ shutdown(): Promise<void>;
1211
+ /**
1212
+ * Resume gateway from saved config (e.g., after server restart).
1213
+ */
1214
+ resume(): Promise<void>;
1215
+ private loadConfig;
1216
+ private saveConfig;
1217
+ private saveLastSeenUid;
1218
+ private loadLastSeenUid;
1219
+ }
1220
+
1221
+ interface RelayBridgeOptions {
1222
+ /** Port for the HTTP bridge server */
1223
+ port: number;
1224
+ /** Shared secret for authenticating requests */
1225
+ secret: string;
1226
+ /** Local Stalwart SMTP host (default: 127.0.0.1) */
1227
+ smtpHost?: string;
1228
+ /** Local Stalwart SMTP submission port (default: 587) */
1229
+ smtpPort?: number;
1230
+ /** Stalwart auth credentials for the sending agent */
1231
+ smtpUser: string;
1232
+ smtpPass: string;
1233
+ }
1234
+ /**
1235
+ * RelayBridge — A local HTTP-to-SMTP bridge that submits email to Stalwart.
1236
+ *
1237
+ * Stalwart then handles DKIM signing, MX resolution, and direct delivery
1238
+ * to the recipient's mail server on port 25. FROM is preserved exactly.
1239
+ *
1240
+ * This bridge is exposed via Cloudflare Tunnel so Cloudflare Workers
1241
+ * (which can't connect to port 25) can trigger outbound email through it.
1242
+ *
1243
+ * For production, deploy on a VPS with proper PTR/FCrDNS for reliable
1244
+ * delivery to all providers (Gmail, Outlook, etc.).
1245
+ */
1246
+ declare class RelayBridge {
1247
+ private server;
1248
+ private options;
1249
+ constructor(options: RelayBridgeOptions);
1250
+ start(): Promise<void>;
1251
+ stop(): void;
1252
+ private handleRequest;
1253
+ private submitToStalwart;
1254
+ }
1255
+ declare function startRelayBridge(options: RelayBridgeOptions): RelayBridge;
1256
+
1257
+ /**
1258
+ * SMS Manager - Google Voice SMS integration
1259
+ *
1260
+ * How it works:
1261
+ * 1. User sets up Google Voice with SMS-to-email forwarding
1262
+ * 2. Incoming SMS arrives at Google Voice -> forwarded to email -> lands in agent inbox
1263
+ * 3. Agent parses forwarded SMS from email body
1264
+ * 4. Outgoing SMS sent via Google Voice web interface (browser automation)
1265
+ *
1266
+ * SMS config is stored in agent metadata under the "sms" key.
1267
+ */
1268
+
1269
+ interface SmsConfig {
1270
+ /** Whether SMS is enabled for this agent */
1271
+ enabled: boolean;
1272
+ /** Google Voice phone number (e.g. +12125551234) */
1273
+ phoneNumber: string;
1274
+ /** The email address Google Voice forwards SMS to (the Gmail used for GV signup) */
1275
+ forwardingEmail: string;
1276
+ /** App password for forwarding email (only needed if different from relay email) */
1277
+ forwardingPassword?: string;
1278
+ /** Whether the GV Gmail is the same as the relay email */
1279
+ sameAsRelay?: boolean;
1280
+ /** Provider (currently only google_voice) */
1281
+ provider: 'google_voice';
1282
+ /** When SMS was configured */
1283
+ configuredAt: string;
1284
+ }
1285
+ interface ParsedSms {
1286
+ from: string;
1287
+ body: string;
1288
+ timestamp: string;
1289
+ raw?: string;
1290
+ }
1291
+ interface SmsMessage {
1292
+ id: string;
1293
+ agentId: string;
1294
+ direction: 'inbound' | 'outbound';
1295
+ phoneNumber: string;
1296
+ body: string;
1297
+ status: 'pending' | 'sent' | 'delivered' | 'failed' | 'received';
1298
+ createdAt: string;
1299
+ metadata?: Record<string, unknown>;
1300
+ }
1301
+ /** Normalize a phone number to E.164-ish format (+1XXXXXXXXXX) */
1302
+ declare function normalizePhoneNumber(raw: string): string | null;
1303
+ /** Validate a phone number (basic) */
1304
+ declare function isValidPhoneNumber(phone: string): boolean;
1305
+ /**
1306
+ * Parse an SMS forwarded from Google Voice via email.
1307
+ * Google Voice forwards SMS with a specific format.
1308
+ *
1309
+ * Known sender addresses:
1310
+ * - voice-noreply@google.com
1311
+ * - *@txt.voice.google.com
1312
+ * - Google Voice <voice-noreply@google.com>
1313
+ */
1314
+ declare function parseGoogleVoiceSms(emailBody: string, emailFrom: string): ParsedSms | null;
1315
+ /**
1316
+ * Extract verification codes from SMS body.
1317
+ * Supports common formats: 6-digit, 4-digit, alphanumeric codes.
1318
+ */
1319
+ declare function extractVerificationCode(smsBody: string): string | null;
1320
+ declare class SmsManager {
1321
+ private db;
1322
+ private initialized;
1323
+ constructor(db: Database$1);
1324
+ private ensureTable;
1325
+ /** Get SMS config from agent metadata */
1326
+ getSmsConfig(agentId: string): SmsConfig | null;
1327
+ /** Save SMS config to agent metadata */
1328
+ saveSmsConfig(agentId: string, config: SmsConfig): void;
1329
+ /** Remove SMS config from agent metadata */
1330
+ removeSmsConfig(agentId: string): void;
1331
+ /** Record an inbound SMS (parsed from email) */
1332
+ recordInbound(agentId: string, parsed: ParsedSms): SmsMessage;
1333
+ /** Record an outbound SMS attempt */
1334
+ recordOutbound(agentId: string, phoneNumber: string, body: string, status?: 'pending' | 'sent' | 'failed'): SmsMessage;
1335
+ /** Update SMS status */
1336
+ updateStatus(id: string, status: SmsMessage['status']): void;
1337
+ /** List SMS messages for an agent */
1338
+ listMessages(agentId: string, opts?: {
1339
+ direction?: 'inbound' | 'outbound';
1340
+ limit?: number;
1341
+ offset?: number;
1342
+ }): SmsMessage[];
1343
+ /** Check for recent verification codes in inbound SMS */
1344
+ checkForVerificationCode(agentId: string, minutesBack?: number): {
1345
+ code: string;
1346
+ from: string;
1347
+ body: string;
1348
+ receivedAt: string;
1349
+ } | null;
1350
+ }
1351
+ /**
1352
+ * SmsPoller — Polls for Google Voice SMS forwarded emails.
1353
+ *
1354
+ * Two modes:
1355
+ * 1. **Same email** (sameAsRelay=true): Hooks into the relay's onInboundMail callback.
1356
+ * The relay poll already fetches emails; SmsPoller filters for GV forwarded SMS.
1357
+ * 2. **Separate email** (sameAsRelay=false): Runs its own IMAP poll against the GV Gmail
1358
+ * using the separate credentials (forwardingEmail + forwardingPassword).
1359
+ *
1360
+ * Parsed SMS messages are stored in the sms_messages table.
1361
+ */
1362
+ declare class SmsPoller {
1363
+ private smsManager;
1364
+ private agentId;
1365
+ private config;
1366
+ private pollTimer;
1367
+ private polling;
1368
+ private lastSeenUid;
1369
+ private firstPollDone;
1370
+ private consecutiveFailures;
1371
+ private readonly POLL_INTERVAL_MS;
1372
+ private readonly MAX_BACKOFF_MS;
1373
+ private readonly CONNECT_TIMEOUT_MS;
1374
+ /** Callback for new inbound SMS */
1375
+ onSmsReceived: ((agentId: string, sms: ParsedSms) => void | Promise<void>) | null;
1376
+ constructor(smsManager: SmsManager, agentId: string, config: SmsConfig);
1377
+ /** Whether this poller needs its own IMAP connection (separate Gmail) */
1378
+ get needsSeparatePoll(): boolean;
1379
+ /**
1380
+ * Process an email from the relay poll (same-email mode).
1381
+ * Called by the relay gateway's onInboundMail when it detects a GV email.
1382
+ * Returns true if the email was an SMS and was processed.
1383
+ */
1384
+ processRelayEmail(from: string, subject: string, body: string): boolean;
1385
+ /**
1386
+ * Start polling the separate GV Gmail for SMS (separate-email mode).
1387
+ * Only call this if needsSeparatePoll is true.
1388
+ */
1389
+ startPolling(): Promise<void>;
1390
+ stopPolling(): void;
1391
+ private scheduleNext;
1392
+ private pollOnce;
1393
+ }
1394
+
1395
+ /**
1396
+ * AgenticMail Anonymous Telemetry
1397
+ *
1398
+ * Collects anonymous usage counts to help improve the product.
1399
+ * NO personal data, API keys, emails, or content is ever collected.
1400
+ *
1401
+ * Opt out: set AGENTICMAIL_TELEMETRY=0 or DO_NOT_TRACK=1
1402
+ *
1403
+ * What we collect:
1404
+ * - Tool call counts (which tools are popular)
1405
+ * - Package version
1406
+ * - Anonymous install ID (random UUID, no PII)
1407
+ * - OS platform (e.g. "darwin", "linux")
1408
+ */
1409
+ /** Set the package version for telemetry events */
1410
+ declare function setTelemetryVersion(version: string): void;
1411
+ /** Record a tool call (fire-and-forget, never throws) */
1412
+ declare function recordToolCall(toolName: string): void;
1413
+ /** Flush remaining events on process exit */
1414
+ declare function flushTelemetry(): void;
1415
+
1416
+ declare function debug(tag: string, message: string): void;
1417
+ declare function debugWarn(tag: string, message: string): void;
1418
+
1419
+ interface DependencyStatus {
1420
+ name: string;
1421
+ installed: boolean;
1422
+ version?: string;
1423
+ description: string;
1424
+ }
1425
+ /**
1426
+ * DependencyChecker detects which external tools are available.
1427
+ * All dependencies are required — setup will auto-install any missing ones.
1428
+ */
1429
+ declare class DependencyChecker {
1430
+ checkAll(): Promise<DependencyStatus[]>;
1431
+ checkDocker(): Promise<DependencyStatus>;
1432
+ checkStalwart(): Promise<DependencyStatus>;
1433
+ checkCloudflared(): Promise<DependencyStatus>;
1434
+ }
1435
+
1436
+ type InstallProgress = (message: string) => void;
1437
+ /**
1438
+ * DependencyInstaller handles installing all external dependencies.
1439
+ * Uses Colima + Docker CLI on macOS (no Docker Desktop GUI).
1440
+ * Uses Docker Engine directly on Linux.
1441
+ */
1442
+ declare class DependencyInstaller {
1443
+ private onProgress;
1444
+ constructor(onProgress?: InstallProgress);
1445
+ /**
1446
+ * Ensure Docker is installed AND the daemon is running.
1447
+ *
1448
+ * Flow:
1449
+ * 1. docker info works? → done
1450
+ * 2. docker CLI + colima exist but not running? → colima start
1451
+ * 3. Nothing installed? → install via brew (colima + docker) or Docker Engine (Linux)
1452
+ */
1453
+ installDocker(): Promise<void>;
1454
+ /** Check if `docker info` succeeds (CLI + daemon both working). */
1455
+ private isDockerReady;
1456
+ /**
1457
+ * macOS: Install Docker via Colima (CLI-only, no GUI, no license dialogs).
1458
+ * Installs colima + docker + docker-compose via Homebrew, then starts Colima.
1459
+ */
1460
+ private installDockerMac;
1461
+ /**
1462
+ * Link docker-compose as a Docker CLI plugin so `docker compose` (v2 syntax) works.
1463
+ * Brew installs docker-compose as a standalone binary, but many tools expect
1464
+ * the `docker compose` subcommand.
1465
+ */
1466
+ private linkComposePlugin;
1467
+ /**
1468
+ * Start Colima and wait for Docker to be ready.
1469
+ */
1470
+ private startColima;
1471
+ /**
1472
+ * Install Docker Engine on Linux using Docker's official convenience script.
1473
+ * Also adds the current user to the docker group for rootless usage.
1474
+ */
1475
+ private installDockerLinux;
1476
+ /**
1477
+ * Wait for Docker daemon on Linux.
1478
+ */
1479
+ private waitForDockerLinux;
1480
+ /**
1481
+ * Windows: Install Docker via WSL2 + Docker Engine, or guide user to Docker Desktop.
1482
+ * Prefers WSL2 with Docker Engine (no GUI needed).
1483
+ */
1484
+ private installDockerWindows;
1485
+ /**
1486
+ * Wait for Docker daemon on Windows.
1487
+ */
1488
+ private waitForDockerWindows;
1489
+ /**
1490
+ * Start the Stalwart mail server Docker container.
1491
+ */
1492
+ startStalwart(composePath: string): Promise<void>;
1493
+ /**
1494
+ * Download and install cloudflared to ~/.agenticmail/bin/cloudflared.
1495
+ * Returns the path to the installed binary.
1496
+ */
1497
+ installCloudflared(): Promise<string>;
1498
+ /**
1499
+ * Install all dependencies. Checks each one and installs if missing.
1500
+ */
1501
+ installAll(composePath: string): Promise<void>;
1502
+ }
1503
+
1504
+ interface ServiceStatus {
1505
+ installed: boolean;
1506
+ running: boolean;
1507
+ platform: 'launchd' | 'systemd' | 'unsupported';
1508
+ servicePath: string | null;
1509
+ }
1510
+ /**
1511
+ * ServiceManager handles auto-start on boot for the AgenticMail API server.
1512
+ * - macOS: LaunchAgent plist (user-level, no sudo needed)
1513
+ * - Linux: systemd user service (user-level, no sudo needed)
1514
+ */
1515
+ declare class ServiceManager {
1516
+ private os;
1517
+ /**
1518
+ * Get the path to the service file.
1519
+ */
1520
+ private getServicePath;
1521
+ /**
1522
+ * Find the Node.js binary path.
1523
+ */
1524
+ private getNodePath;
1525
+ /**
1526
+ * Find the API server entry point.
1527
+ * Searches common locations where agenticmail is installed.
1528
+ */
1529
+ private getApiEntryPath;
1530
+ /**
1531
+ * Cache the API entry path so the service can find it later.
1532
+ */
1533
+ cacheApiEntryPath(entryPath: string): void;
1534
+ /**
1535
+ * Get the current package version.
1536
+ */
1537
+ private getVersion;
1538
+ /**
1539
+ * Generate a wrapper script that waits for Docker before starting the API.
1540
+ * This ensures AgenticMail doesn't fail on boot when Docker is still loading.
1541
+ */
1542
+ private generateStartScript;
1543
+ /**
1544
+ * Generate the launchd plist content for macOS.
1545
+ * More robust than OpenClaw's plist:
1546
+ * - Wrapper script waits for Docker + Stalwart before starting
1547
+ * - KeepAlive: true (unconditional — always restart, not just on crash)
1548
+ * - SoftResourceLimits for file descriptors (email servers need many)
1549
+ * - StartInterval as backup heartbeat (checks every 5 min)
1550
+ * - Service version tracking in env vars
1551
+ */
1552
+ private generatePlist;
1553
+ /**
1554
+ * Generate the systemd user service content for Linux.
1555
+ * More robust than basic services:
1556
+ * - Wrapper script waits for Docker + Stalwart
1557
+ * - Restart=always (unconditional)
1558
+ * - WatchdogSec for health monitoring
1559
+ * - File descriptor limits
1560
+ * - Proper dependency ordering
1561
+ */
1562
+ private generateSystemdUnit;
1563
+ /**
1564
+ * Install the auto-start service.
1565
+ */
1566
+ install(): {
1567
+ installed: boolean;
1568
+ message: string;
1569
+ };
1570
+ /**
1571
+ * Uninstall the auto-start service.
1572
+ */
1573
+ uninstall(): {
1574
+ removed: boolean;
1575
+ message: string;
1576
+ };
1577
+ /**
1578
+ * Get the current service status.
1579
+ */
1580
+ status(): ServiceStatus;
1581
+ /**
1582
+ * Reinstall the service (useful after config changes or updates).
1583
+ */
1584
+ reinstall(): {
1585
+ installed: boolean;
1586
+ message: string;
1587
+ };
1588
+ }
1589
+
1590
+ interface SetupConfig {
1591
+ masterKey: string;
1592
+ stalwart: {
1593
+ url: string;
1594
+ adminUser: string;
1595
+ adminPassword: string;
1596
+ };
1597
+ smtp: {
1598
+ host: string;
1599
+ port: number;
1600
+ };
1601
+ imap: {
1602
+ host: string;
1603
+ port: number;
1604
+ };
1605
+ api: {
1606
+ port: number;
1607
+ host: string;
1608
+ };
1609
+ dataDir: string;
1610
+ }
1611
+ interface SetupResult {
1612
+ configPath: string;
1613
+ envPath: string;
1614
+ config: SetupConfig;
1615
+ isNew: boolean;
1616
+ }
1617
+ /**
1618
+ * SetupManager orchestrates AgenticMail initialization:
1619
+ * config generation, dependency installation, and service startup.
1620
+ * All dependencies are required and auto-installed.
1621
+ */
1622
+ declare class SetupManager {
1623
+ private checker;
1624
+ private installer;
1625
+ constructor(onProgress?: InstallProgress);
1626
+ /**
1627
+ * Check all dependencies and return their status.
1628
+ */
1629
+ checkDependencies(): Promise<DependencyStatus[]>;
1630
+ /**
1631
+ * Install all missing dependencies automatically.
1632
+ * Docker → Stalwart → cloudflared. Nothing is optional.
1633
+ */
1634
+ installAll(composePath: string): Promise<void>;
1635
+ /**
1636
+ * Ensure a specific dependency is installed.
1637
+ */
1638
+ ensureDocker(): Promise<void>;
1639
+ ensureStalwart(composePath?: string): Promise<void>;
1640
+ ensureCloudflared(): Promise<string>;
1641
+ /**
1642
+ * Get the path to docker-compose.yml.
1643
+ * Prefers ~/.agenticmail/docker-compose.yml (standalone),
1644
+ * falls back to monorepo location.
1645
+ */
1646
+ getComposePath(): string;
1647
+ /**
1648
+ * Initialize AgenticMail config files.
1649
+ * If config already exists, returns the existing config without overwriting.
1650
+ * Always regenerates Docker files to keep passwords in sync.
1651
+ */
1652
+ initConfig(): SetupResult;
1653
+ /**
1654
+ * Generate docker-compose.yml and stalwart.toml in ~/.agenticmail/
1655
+ * with the correct admin password from config.
1656
+ */
1657
+ private generateDockerFiles;
1658
+ /**
1659
+ * Check if config has already been initialized.
1660
+ */
1661
+ isInitialized(): boolean;
1662
+ }
1663
+
1664
+ 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 ParsedSms, 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, ServiceManager, type ServiceStatus, type SetupConfig, SetupManager, type SetupResult, type Severity, type SmsConfig, SmsManager, type SmsMessage, SmsPoller, type SpamCategory, type SpamResult, type SpamRuleMatch, StalwartAdmin, type StalwartAdminOptions, type StalwartPrincipal, type TunnelConfig, TunnelManager, WARNING_THRESHOLD, type WatcherOptions, buildInboundSecurityAdvisory, closeDatabase, createTestDatabase, debug, debugWarn, ensureDataDir, extractVerificationCode, flushTelemetry, getDatabase, isInternalEmail, isValidPhoneNumber, normalizePhoneNumber, parseEmail, parseGoogleVoiceSms, recordToolCall, resolveConfig, sanitizeEmail, saveConfig, scanOutboundEmail, scoreEmail, setTelemetryVersion, startRelayBridge };