@agenticmail/enterprise 0.5.52 → 0.5.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/chunk-4ZVC4XJY.js +3563 -0
  2. package/dist/chunk-G54QH5PZ.js +9085 -0
  3. package/dist/chunk-GI5RMYH6.js +37 -0
  4. package/dist/chunk-HAN6MNXJ.js +374 -0
  5. package/dist/chunk-L447KLHG.js +13099 -0
  6. package/dist/chunk-ORN3ILZD.js +48 -0
  7. package/dist/chunk-QIBEF5G3.js +898 -0
  8. package/dist/chunk-WA6WJN3D.js +2115 -0
  9. package/dist/cidr-MQSAKEA6.js +17 -0
  10. package/dist/cli-build-skill-ZD5FURGY.js +235 -0
  11. package/dist/cli-recover-XKHGIGGR.js +97 -0
  12. package/dist/cli-submit-skill-R7HUAMYR.js +162 -0
  13. package/dist/cli-validate-BDWKT6KH.js +148 -0
  14. package/dist/cli-verify-RTYVUWD7.js +98 -0
  15. package/dist/config-store-44Y6KOVX.js +58 -0
  16. package/dist/db-adapter-FJZ5BESQ.js +7 -0
  17. package/dist/domain-lock-7EMDJXFQ.js +7 -0
  18. package/dist/dynamodb-CDDPVEXF.js +424 -0
  19. package/dist/factory-2SRP3B3U.js +9 -0
  20. package/dist/firewall-RTIDM4NY.js +10 -0
  21. package/dist/imap-flow-QTNT4Y75.js +44953 -0
  22. package/dist/index.js +322 -1
  23. package/dist/managed-2CZ3CSGR.js +17 -0
  24. package/dist/mongodb-UEHZUXYD.js +320 -0
  25. package/dist/mysql-4BBS773W.js +575 -0
  26. package/dist/nodemailer-JCKCTRMI.js +11711 -0
  27. package/dist/postgres-BCVODZLS.js +597 -0
  28. package/dist/providers-VPFJNW4H.js +17 -0
  29. package/dist/resolve-driver-ZLJYEK72.js +27 -0
  30. package/dist/routes-M5J73UQY.js +5783 -0
  31. package/dist/runtime-KQFFAIFR.js +47 -0
  32. package/dist/server-4FHO44MK.js +12 -0
  33. package/dist/setup-ZWEA6MUM.js +20 -0
  34. package/dist/skills-WMCZVXBK.js +14 -0
  35. package/dist/sqlite-K6HN7XRU.js +491 -0
  36. package/dist/turso-BBUTZACL.js +496 -0
  37. package/package.json +2 -2
  38. package/src/agenticmail/index.ts +2 -0
  39. package/src/agenticmail/providers/imap.ts +454 -0
  40. package/src/agenticmail/providers/index.ts +4 -2
@@ -0,0 +1,454 @@
1
+ /**
2
+ * IMAP/SMTP Email Provider
3
+ *
4
+ * The simplest and most universal email provider.
5
+ * Works with ANY email system that supports IMAP + SMTP.
6
+ *
7
+ * How it works in practice:
8
+ * 1. IT admin creates agent-sales@company.com in Microsoft 365 / Google Workspace / Exchange / etc.
9
+ * 2. Creates an app password (or uses the regular password if MFA isn't enforced)
10
+ * 3. In the enterprise dashboard, enters: email + password + IMAP host + SMTP host
11
+ * 4. Agent connects just like Outlook or Thunderbird would
12
+ *
13
+ * This is the "just works" path. No OAuth apps to register,
14
+ * no consent flows, no token refresh. Just email + password.
15
+ *
16
+ * For Microsoft 365: IMAP host = outlook.office365.com, SMTP host = smtp.office365.com
17
+ * For Google Workspace: IMAP host = imap.gmail.com, SMTP host = smtp.gmail.com (needs app password)
18
+ * For self-hosted Exchange: whatever your IT gives you
19
+ */
20
+
21
+ import type {
22
+ IEmailProvider, AgentEmailIdentity, EmailProvider,
23
+ EmailMessage, EmailEnvelope, EmailFolder,
24
+ SendEmailOptions, SearchCriteria,
25
+ } from '../types.js';
26
+
27
+ // ─── Extended identity for IMAP (includes server details) ────────────
28
+
29
+ export interface ImapEmailIdentity extends AgentEmailIdentity {
30
+ /** IMAP server hostname */
31
+ imapHost: string;
32
+ /** IMAP port (default 993) */
33
+ imapPort?: number;
34
+ /** SMTP server hostname */
35
+ smtpHost: string;
36
+ /** SMTP port (default 587) */
37
+ smtpPort?: number;
38
+ /** Password or app password (stored in accessToken field for compatibility) */
39
+ password?: string;
40
+ }
41
+
42
+ // ─── Well-known server presets ───────────────────────────────────────
43
+
44
+ export const IMAP_PRESETS: Record<string, { imapHost: string; imapPort: number; smtpHost: string; smtpPort: number }> = {
45
+ 'microsoft365': { imapHost: 'outlook.office365.com', imapPort: 993, smtpHost: 'smtp.office365.com', smtpPort: 587 },
46
+ 'office365': { imapHost: 'outlook.office365.com', imapPort: 993, smtpHost: 'smtp.office365.com', smtpPort: 587 },
47
+ 'outlook': { imapHost: 'outlook.office365.com', imapPort: 993, smtpHost: 'smtp.office365.com', smtpPort: 587 },
48
+ 'gmail': { imapHost: 'imap.gmail.com', imapPort: 993, smtpHost: 'smtp.gmail.com', smtpPort: 587 },
49
+ 'google': { imapHost: 'imap.gmail.com', imapPort: 993, smtpHost: 'smtp.gmail.com', smtpPort: 587 },
50
+ 'yahoo': { imapHost: 'imap.mail.yahoo.com', imapPort: 993, smtpHost: 'smtp.mail.yahoo.com', smtpPort: 465 },
51
+ 'zoho': { imapHost: 'imap.zoho.com', imapPort: 993, smtpHost: 'smtp.zoho.com', smtpPort: 587 },
52
+ 'fastmail': { imapHost: 'imap.fastmail.com', imapPort: 993, smtpHost: 'smtp.fastmail.com', smtpPort: 587 },
53
+ 'icloud': { imapHost: 'imap.mail.me.com', imapPort: 993, smtpHost: 'smtp.mail.me.com', smtpPort: 587 },
54
+ };
55
+
56
+ /**
57
+ * Detect IMAP/SMTP settings from an email domain.
58
+ * Returns preset if known, otherwise guesses based on domain.
59
+ */
60
+ export function detectImapSettings(email: string): { imapHost: string; imapPort: number; smtpHost: string; smtpPort: number } | null {
61
+ const domain = email.split('@')[1]?.toLowerCase();
62
+ if (!domain) return null;
63
+
64
+ // Check presets
65
+ for (const [key, preset] of Object.entries(IMAP_PRESETS)) {
66
+ if (domain.includes(key) || domain === 'gmail.com' || domain === 'outlook.com' || domain === 'hotmail.com') {
67
+ if (domain === 'gmail.com' || domain.endsWith('.google.com')) return IMAP_PRESETS.gmail;
68
+ if (domain === 'outlook.com' || domain === 'hotmail.com' || domain.endsWith('.onmicrosoft.com')) return IMAP_PRESETS.microsoft365;
69
+ }
70
+ }
71
+
72
+ // For custom domains, try common patterns
73
+ // Many companies using M365 will have outlook.office365.com
74
+ // but we can't know for sure, so return null and let the admin specify
75
+ return null;
76
+ }
77
+
78
+ // ─── IMAP Provider ──────────────────────────────────────────────────
79
+
80
+ export class ImapEmailProvider implements IEmailProvider {
81
+ readonly provider: EmailProvider = 'imap';
82
+ private identity: ImapEmailIdentity | null = null;
83
+
84
+ // IMAP connection (lazy-loaded to avoid bundling the dep if not used)
85
+ private imapClient: any = null;
86
+ private smtpClient: any = null;
87
+
88
+ private getIdentity(): ImapEmailIdentity {
89
+ if (!this.identity) throw new Error('Not connected — call connect() first');
90
+ return this.identity;
91
+ }
92
+
93
+ // ─── Connection ─────────────────────────────────────
94
+
95
+ async connect(identity: AgentEmailIdentity): Promise<void> {
96
+ const imapIdentity = identity as ImapEmailIdentity;
97
+
98
+ // Validate required fields
99
+ if (!imapIdentity.imapHost) throw new Error('IMAP host is required');
100
+ if (!imapIdentity.smtpHost) throw new Error('SMTP host is required');
101
+
102
+ this.identity = imapIdentity;
103
+
104
+ // Try to load imapflow for IMAP connection
105
+ try {
106
+ const { ImapFlow } = await import('imapflow');
107
+ this.imapClient = new ImapFlow({
108
+ host: imapIdentity.imapHost,
109
+ port: imapIdentity.imapPort || 993,
110
+ secure: true,
111
+ auth: {
112
+ user: imapIdentity.email,
113
+ pass: imapIdentity.password || imapIdentity.accessToken,
114
+ },
115
+ logger: false,
116
+ });
117
+ await this.imapClient.connect();
118
+ } catch (err: any) {
119
+ // If imapflow isn't installed, we'll use a fetch-based fallback for providers that support REST
120
+ if (err.code === 'ERR_MODULE_NOT_FOUND' || err.message?.includes('Cannot find')) {
121
+ console.warn('[imap-provider] imapflow not installed — IMAP operations will fail. Install with: npm install imapflow');
122
+ this.imapClient = null;
123
+ } else {
124
+ throw new Error(`Failed to connect to IMAP server ${imapIdentity.imapHost}: ${err.message}`);
125
+ }
126
+ }
127
+ }
128
+
129
+ async disconnect(): Promise<void> {
130
+ if (this.imapClient) {
131
+ try { await this.imapClient.logout(); } catch {}
132
+ this.imapClient = null;
133
+ }
134
+ this.identity = null;
135
+ }
136
+
137
+ // ─── List / Read ────────────────────────────────────
138
+
139
+ async listMessages(folder: string, opts?: { limit?: number; offset?: number }): Promise<EmailEnvelope[]> {
140
+ if (!this.imapClient) throw new Error('IMAP not connected. Ensure imapflow is installed and connection succeeded.');
141
+
142
+ const limit = opts?.limit || 20;
143
+ const lock = await this.imapClient.getMailboxLock(folder || 'INBOX');
144
+ try {
145
+ const envelopes: EmailEnvelope[] = [];
146
+ const status = await this.imapClient.status(folder || 'INBOX', { messages: true });
147
+ const total = status.messages || 0;
148
+ const start = Math.max(1, total - (opts?.offset || 0) - limit + 1);
149
+ const end = total - (opts?.offset || 0);
150
+
151
+ if (start > end || end < 1) return [];
152
+
153
+ for await (const msg of this.imapClient.fetch(`${start}:${end}`, { envelope: true, flags: true, bodyStructure: true })) {
154
+ envelopes.push({
155
+ uid: String(msg.uid),
156
+ from: {
157
+ name: msg.envelope?.from?.[0]?.name || undefined,
158
+ email: msg.envelope?.from?.[0]?.address || '',
159
+ },
160
+ to: (msg.envelope?.to || []).map((t: any) => ({
161
+ name: t.name || undefined,
162
+ email: t.address || '',
163
+ })),
164
+ subject: msg.envelope?.subject || '',
165
+ date: msg.envelope?.date?.toISOString() || '',
166
+ read: msg.flags?.has('\\Seen') || false,
167
+ flagged: msg.flags?.has('\\Flagged') || false,
168
+ hasAttachments: msg.bodyStructure?.childNodes?.length > 1 || false,
169
+ });
170
+ }
171
+
172
+ return envelopes.reverse(); // newest first
173
+ } finally {
174
+ lock.release();
175
+ }
176
+ }
177
+
178
+ async readMessage(uid: string, folder?: string): Promise<EmailMessage> {
179
+ if (!this.imapClient) throw new Error('IMAP not connected');
180
+
181
+ const lock = await this.imapClient.getMailboxLock(folder || 'INBOX');
182
+ try {
183
+ const msg = await this.imapClient.fetchOne(uid, {
184
+ envelope: true,
185
+ flags: true,
186
+ source: true, // full RFC822 message
187
+ }, { uid: true });
188
+
189
+ if (!msg) throw new Error(`Message ${uid} not found`);
190
+
191
+ // Parse the raw email source
192
+ const source = msg.source?.toString('utf-8') || '';
193
+ const bodyMatch = source.match(/\r?\n\r?\n([\s\S]*)/);
194
+ const body = bodyMatch ? bodyMatch[1] : '';
195
+
196
+ return {
197
+ uid: String(msg.uid),
198
+ from: {
199
+ name: msg.envelope?.from?.[0]?.name || undefined,
200
+ email: msg.envelope?.from?.[0]?.address || '',
201
+ },
202
+ to: (msg.envelope?.to || []).map((t: any) => ({
203
+ name: t.name || undefined,
204
+ email: t.address || '',
205
+ })),
206
+ cc: (msg.envelope?.cc || []).map((c: any) => ({
207
+ name: c.name || undefined,
208
+ email: c.address || '',
209
+ })),
210
+ subject: msg.envelope?.subject || '',
211
+ body,
212
+ date: msg.envelope?.date?.toISOString() || '',
213
+ read: msg.flags?.has('\\Seen') || false,
214
+ flagged: msg.flags?.has('\\Flagged') || false,
215
+ folder: folder || 'INBOX',
216
+ messageId: msg.envelope?.messageId || undefined,
217
+ inReplyTo: msg.envelope?.inReplyTo || undefined,
218
+ };
219
+ } finally {
220
+ lock.release();
221
+ }
222
+ }
223
+
224
+ async searchMessages(criteria: SearchCriteria): Promise<EmailEnvelope[]> {
225
+ if (!this.imapClient) throw new Error('IMAP not connected');
226
+
227
+ const lock = await this.imapClient.getMailboxLock('INBOX');
228
+ try {
229
+ const query: any = {};
230
+ if (criteria.from) query.from = criteria.from;
231
+ if (criteria.to) query.to = criteria.to;
232
+ if (criteria.subject) query.subject = criteria.subject;
233
+ if (criteria.text) query.body = criteria.text;
234
+ if (criteria.since) query.since = new Date(criteria.since);
235
+ if (criteria.before) query.before = new Date(criteria.before);
236
+ if (criteria.seen === true) query.seen = true;
237
+ if (criteria.seen === false) query.unseen = true;
238
+
239
+ const uids = await this.imapClient.search(query, { uid: true });
240
+ if (!uids.length) return [];
241
+
242
+ const envelopes: EmailEnvelope[] = [];
243
+ const uidRange = uids.slice(-50).join(','); // last 50 results
244
+
245
+ for await (const msg of this.imapClient.fetch(uidRange, { envelope: true, flags: true }, { uid: true })) {
246
+ envelopes.push({
247
+ uid: String(msg.uid),
248
+ from: {
249
+ name: msg.envelope?.from?.[0]?.name || undefined,
250
+ email: msg.envelope?.from?.[0]?.address || '',
251
+ },
252
+ to: (msg.envelope?.to || []).map((t: any) => ({
253
+ name: t.name || undefined,
254
+ email: t.address || '',
255
+ })),
256
+ subject: msg.envelope?.subject || '',
257
+ date: msg.envelope?.date?.toISOString() || '',
258
+ read: msg.flags?.has('\\Seen') || false,
259
+ flagged: msg.flags?.has('\\Flagged') || false,
260
+ hasAttachments: false,
261
+ });
262
+ }
263
+
264
+ return envelopes.reverse();
265
+ } finally {
266
+ lock.release();
267
+ }
268
+ }
269
+
270
+ async listFolders(): Promise<EmailFolder[]> {
271
+ if (!this.imapClient) throw new Error('IMAP not connected');
272
+
273
+ const folders: EmailFolder[] = [];
274
+ const mailboxes = await this.imapClient.list();
275
+
276
+ for (const mb of mailboxes) {
277
+ try {
278
+ const status = await this.imapClient.status(mb.path, { messages: true, unseen: true });
279
+ folders.push({
280
+ name: mb.name,
281
+ path: mb.path,
282
+ unread: status.unseen || 0,
283
+ total: status.messages || 0,
284
+ });
285
+ } catch {
286
+ folders.push({ name: mb.name, path: mb.path, unread: 0, total: 0 });
287
+ }
288
+ }
289
+
290
+ return folders;
291
+ }
292
+
293
+ async createFolder(name: string): Promise<void> {
294
+ if (!this.imapClient) throw new Error('IMAP not connected');
295
+ await this.imapClient.mailboxCreate(name);
296
+ }
297
+
298
+ // ─── Send (via SMTP) ───────────────────────────────
299
+
300
+ async send(options: SendEmailOptions): Promise<{ messageId: string }> {
301
+ const identity = this.getIdentity();
302
+
303
+ try {
304
+ const nodemailer = await import('nodemailer');
305
+ const transporter = nodemailer.createTransport({
306
+ host: identity.smtpHost,
307
+ port: identity.smtpPort || 587,
308
+ secure: (identity.smtpPort || 587) === 465,
309
+ auth: {
310
+ user: identity.email,
311
+ pass: identity.password || identity.accessToken,
312
+ },
313
+ });
314
+
315
+ const result = await transporter.sendMail({
316
+ from: identity.email,
317
+ to: options.to,
318
+ cc: options.cc,
319
+ bcc: options.bcc,
320
+ subject: options.subject,
321
+ text: options.body,
322
+ html: options.html,
323
+ inReplyTo: options.inReplyTo,
324
+ references: options.references?.join(' '),
325
+ attachments: options.attachments?.map(a => ({
326
+ filename: a.filename,
327
+ content: a.content,
328
+ contentType: a.contentType,
329
+ encoding: a.encoding as any,
330
+ })),
331
+ });
332
+
333
+ return { messageId: result.messageId || `smtp-${Date.now()}` };
334
+ } catch (err: any) {
335
+ if (err.code === 'ERR_MODULE_NOT_FOUND' || err.message?.includes('Cannot find')) {
336
+ throw new Error('nodemailer is required for SMTP sending. Install with: npm install nodemailer');
337
+ }
338
+ throw new Error(`SMTP send failed: ${err.message}`);
339
+ }
340
+ }
341
+
342
+ async reply(uid: string, body: string, replyAll = false): Promise<{ messageId: string }> {
343
+ const original = await this.readMessage(uid);
344
+ const to = replyAll
345
+ ? [original.from.email, ...(original.to || []).map(t => t.email), ...(original.cc || []).map(c => c.email)]
346
+ .filter(e => e !== this.identity?.email)
347
+ .join(', ')
348
+ : original.from.email;
349
+
350
+ return this.send({
351
+ to,
352
+ subject: original.subject.startsWith('Re:') ? original.subject : `Re: ${original.subject}`,
353
+ body,
354
+ inReplyTo: original.messageId,
355
+ references: original.references
356
+ ? [...original.references, original.messageId!]
357
+ : original.messageId ? [original.messageId] : undefined,
358
+ });
359
+ }
360
+
361
+ async forward(uid: string, to: string, body?: string): Promise<{ messageId: string }> {
362
+ const original = await this.readMessage(uid);
363
+ return this.send({
364
+ to,
365
+ subject: `Fwd: ${original.subject}`,
366
+ body: (body ? body + '\n\n' : '') +
367
+ `---------- Forwarded message ----------\n` +
368
+ `From: ${original.from.email}\n` +
369
+ `Date: ${original.date}\n` +
370
+ `Subject: ${original.subject}\n\n` +
371
+ original.body,
372
+ });
373
+ }
374
+
375
+ // ─── Organize ───────────────────────────────────────
376
+
377
+ async moveMessage(uid: string, toFolder: string, _fromFolder?: string): Promise<void> {
378
+ if (!this.imapClient) throw new Error('IMAP not connected');
379
+ const lock = await this.imapClient.getMailboxLock(_fromFolder || 'INBOX');
380
+ try {
381
+ await this.imapClient.messageMove(uid, toFolder, { uid: true });
382
+ } finally {
383
+ lock.release();
384
+ }
385
+ }
386
+
387
+ async deleteMessage(uid: string, folder?: string): Promise<void> {
388
+ if (!this.imapClient) throw new Error('IMAP not connected');
389
+ const lock = await this.imapClient.getMailboxLock(folder || 'INBOX');
390
+ try {
391
+ await this.imapClient.messageDelete(uid, { uid: true });
392
+ } finally {
393
+ lock.release();
394
+ }
395
+ }
396
+
397
+ async markRead(uid: string, folder?: string): Promise<void> {
398
+ if (!this.imapClient) throw new Error('IMAP not connected');
399
+ const lock = await this.imapClient.getMailboxLock(folder || 'INBOX');
400
+ try {
401
+ await this.imapClient.messageFlagsAdd(uid, ['\\Seen'], { uid: true });
402
+ } finally {
403
+ lock.release();
404
+ }
405
+ }
406
+
407
+ async markUnread(uid: string, folder?: string): Promise<void> {
408
+ if (!this.imapClient) throw new Error('IMAP not connected');
409
+ const lock = await this.imapClient.getMailboxLock(folder || 'INBOX');
410
+ try {
411
+ await this.imapClient.messageFlagsRemove(uid, ['\\Seen'], { uid: true });
412
+ } finally {
413
+ lock.release();
414
+ }
415
+ }
416
+
417
+ async flagMessage(uid: string, folder?: string): Promise<void> {
418
+ if (!this.imapClient) throw new Error('IMAP not connected');
419
+ const lock = await this.imapClient.getMailboxLock(folder || 'INBOX');
420
+ try {
421
+ await this.imapClient.messageFlagsAdd(uid, ['\\Flagged'], { uid: true });
422
+ } finally {
423
+ lock.release();
424
+ }
425
+ }
426
+
427
+ async unflagMessage(uid: string, folder?: string): Promise<void> {
428
+ if (!this.imapClient) throw new Error('IMAP not connected');
429
+ const lock = await this.imapClient.getMailboxLock(folder || 'INBOX');
430
+ try {
431
+ await this.imapClient.messageFlagsRemove(uid, ['\\Flagged'], { uid: true });
432
+ } finally {
433
+ lock.release();
434
+ }
435
+ }
436
+
437
+ // ─── Batch ──────────────────────────────────────────
438
+
439
+ async batchMarkRead(uids: string[], folder?: string): Promise<void> {
440
+ for (const uid of uids) await this.markRead(uid, folder);
441
+ }
442
+
443
+ async batchMarkUnread(uids: string[], folder?: string): Promise<void> {
444
+ for (const uid of uids) await this.markUnread(uid, folder);
445
+ }
446
+
447
+ async batchMove(uids: string[], toFolder: string, fromFolder?: string): Promise<void> {
448
+ for (const uid of uids) await this.moveMessage(uid, toFolder, fromFolder);
449
+ }
450
+
451
+ async batchDelete(uids: string[], folder?: string): Promise<void> {
452
+ for (const uid of uids) await this.deleteMessage(uid, folder);
453
+ }
454
+ }
@@ -9,17 +9,19 @@
9
9
 
10
10
  export { MicrosoftEmailProvider } from './microsoft.js';
11
11
  export { GoogleEmailProvider } from './google.js';
12
+ export { ImapEmailProvider, IMAP_PRESETS, detectImapSettings } from './imap.js';
13
+ export type { ImapEmailIdentity } from './imap.js';
12
14
 
13
15
  import type { IEmailProvider, EmailProvider } from '../types.js';
14
16
  import { MicrosoftEmailProvider } from './microsoft.js';
15
17
  import { GoogleEmailProvider } from './google.js';
18
+ import { ImapEmailProvider } from './imap.js';
16
19
 
17
20
  export function createEmailProvider(provider: EmailProvider): IEmailProvider {
18
21
  switch (provider) {
19
22
  case 'microsoft': return new MicrosoftEmailProvider();
20
23
  case 'google': return new GoogleEmailProvider();
21
- case 'imap':
22
- throw new Error('Generic IMAP provider not yet implemented — use Microsoft or Google');
24
+ case 'imap': return new ImapEmailProvider();
23
25
  default:
24
26
  throw new Error(`Unknown email provider: ${provider}`);
25
27
  }