@happyvertical/email 0.80.0 → 0.80.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,2165 +1,1566 @@
1
+ import { a as ConnectionError, c as FolderNotFoundError, d as SendError, f as TimeoutError, i as AuthorizationError, l as InvalidMessageError, n as AttachmentError, o as EmailError, r as AuthenticationError, s as FolderExistsError, t as BaseEmailClient, u as MessageNotFoundError } from "./chunks/base-sCad2IoJ.js";
1
2
  import { google } from "googleapis";
2
3
  import { simpleParser } from "mailparser";
3
- import { createLogger } from "@happyvertical/logger";
4
4
  import { ImapFlow } from "imapflow";
5
5
  import Pop3Command from "node-pop3";
6
6
  import nodemailer from "nodemailer";
7
- class EmailError extends Error {
8
- code;
9
- provider;
10
- cause;
11
- constructor(message, code, provider, cause) {
12
- super(message);
13
- this.name = "EmailError";
14
- this.code = code;
15
- this.provider = provider;
16
- this.cause = cause;
17
- }
18
- }
19
- class ConnectionError extends EmailError {
20
- constructor(message, provider, cause) {
21
- super(message, "CONNECTION_ERROR", provider, cause);
22
- this.name = "ConnectionError";
23
- }
24
- }
25
- class TimeoutError extends EmailError {
26
- constructor(message, provider, cause) {
27
- super(message, "TIMEOUT_ERROR", provider, cause);
28
- this.name = "TimeoutError";
29
- }
30
- }
31
- class AuthenticationError extends EmailError {
32
- constructor(message, provider, cause) {
33
- super(message, "AUTHENTICATION_ERROR", provider, cause);
34
- this.name = "AuthenticationError";
35
- }
36
- }
37
- class AuthorizationError extends EmailError {
38
- constructor(message, provider, cause) {
39
- super(message, "AUTHORIZATION_ERROR", provider, cause);
40
- this.name = "AuthorizationError";
41
- }
42
- }
43
- class MessageNotFoundError extends EmailError {
44
- messageId;
45
- constructor(messageId, provider) {
46
- super(`Message not found: ${messageId}`, "MESSAGE_NOT_FOUND", provider);
47
- this.name = "MessageNotFoundError";
48
- this.messageId = messageId;
49
- }
50
- }
51
- class InvalidMessageError extends EmailError {
52
- constructor(message, provider, cause) {
53
- super(message, "INVALID_MESSAGE", provider, cause);
54
- this.name = "InvalidMessageError";
55
- }
56
- }
57
- class FolderNotFoundError extends EmailError {
58
- folder;
59
- constructor(folder, provider) {
60
- super(`Folder not found: ${folder}`, "FOLDER_NOT_FOUND", provider);
61
- this.name = "FolderNotFoundError";
62
- this.folder = folder;
63
- }
64
- }
65
- class FolderExistsError extends EmailError {
66
- folder;
67
- constructor(folder, provider) {
68
- super(`Folder already exists: ${folder}`, "FOLDER_EXISTS", provider);
69
- this.name = "FolderExistsError";
70
- this.folder = folder;
71
- }
72
- }
73
- class SendError extends EmailError {
74
- accepted;
75
- rejected;
76
- constructor(message, accepted, rejected, provider, cause) {
77
- super(message, "SEND_ERROR", provider, cause);
78
- this.name = "SendError";
79
- this.accepted = accepted;
80
- this.rejected = rejected;
81
- }
82
- }
83
- class AttachmentError extends EmailError {
84
- filename;
85
- constructor(message, filename, provider, cause) {
86
- super(message, "ATTACHMENT_ERROR", provider, cause);
87
- this.name = "AttachmentError";
88
- this.filename = filename;
89
- }
90
- }
91
- class BaseEmailClient {
92
- config;
93
- logger;
94
- connected = false;
95
- constructor(config) {
96
- this.config = this.validateConfig(config);
97
- this.logger = config.logger || createLogger({ level: "info" });
98
- }
99
- // ========================================================================
100
- // Connection management
101
- // ========================================================================
102
- isConnected() {
103
- return this.connected;
104
- }
105
- // ========================================================================
106
- // Validation helpers
107
- // ========================================================================
108
- validateConfig(config) {
109
- if (!config.type) {
110
- throw new EmailError("Email client type is required", "INVALID_CONFIG");
111
- }
112
- return config;
113
- }
114
- validateEmail(email) {
115
- if (!email.address) {
116
- throw new InvalidMessageError(
117
- "Email address is required",
118
- this.getAdapter()
119
- );
120
- }
121
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
122
- if (!emailRegex.test(email.address)) {
123
- throw new InvalidMessageError(
124
- `Invalid email address: ${email.address}`,
125
- this.getAdapter()
126
- );
127
- }
128
- }
129
- validateMessage(message) {
130
- if (!message.from) {
131
- throw new InvalidMessageError(
132
- "Message must have a sender (from)",
133
- this.getAdapter()
134
- );
135
- }
136
- this.validateEmail(message.from);
137
- if (!message.to || message.to.length === 0) {
138
- throw new InvalidMessageError(
139
- "Message must have at least one recipient (to)",
140
- this.getAdapter()
141
- );
142
- }
143
- for (const recipient of message.to) {
144
- this.validateEmail(recipient);
145
- }
146
- if (message.cc) {
147
- for (const recipient of message.cc) {
148
- this.validateEmail(recipient);
149
- }
150
- }
151
- if (message.bcc) {
152
- for (const recipient of message.bcc) {
153
- this.validateEmail(recipient);
154
- }
155
- }
156
- if (!message.subject) {
157
- throw new InvalidMessageError(
158
- "Message must have a subject",
159
- this.getAdapter()
160
- );
161
- }
162
- if (!message.text && !message.html) {
163
- throw new InvalidMessageError(
164
- "Message must have either text or HTML content",
165
- this.getAdapter()
166
- );
167
- }
168
- }
169
- // ========================================================================
170
- // Error mapping
171
- // ========================================================================
172
- mapError(error) {
173
- if (error instanceof EmailError) {
174
- return error;
175
- }
176
- if (error instanceof Error) {
177
- return new EmailError(
178
- error.message,
179
- "UNKNOWN_ERROR",
180
- this.getAdapter(),
181
- error
182
- );
183
- }
184
- return new EmailError(String(error), "UNKNOWN_ERROR", this.getAdapter());
185
- }
186
- // ========================================================================
187
- // Utility methods
188
- // ========================================================================
189
- normalizeMessageIds(messageId) {
190
- return Array.isArray(messageId) ? messageId : [messageId];
191
- }
192
- debug(message, data) {
193
- if (this.config.debug) {
194
- this.logger.debug(message, data);
195
- }
196
- }
197
- }
198
- class GmailAdapter extends BaseEmailClient {
199
- gmail = null;
200
- options;
201
- auth = null;
202
- constructor(options) {
203
- super({
204
- type: "gmail",
205
- debug: options.debug
206
- });
207
- this.options = options;
208
- }
209
- // ========================================================================
210
- // Connection management
211
- // ========================================================================
212
- async connect() {
213
- try {
214
- const auth = new google.auth.OAuth2(
215
- this.options.auth.clientId,
216
- this.options.auth.clientSecret
217
- );
218
- auth.setCredentials({
219
- refresh_token: this.options.auth.refreshToken,
220
- access_token: this.options.auth.accessToken
221
- });
222
- this.auth = auth;
223
- this.gmail = google.gmail({ version: "v1", auth });
224
- await this.gmail.users.getProfile({ userId: this.getUserId() });
225
- this.connected = true;
226
- this.debug("Connected to Gmail API");
227
- } catch (error) {
228
- throw this.mapGmailError(error);
229
- }
230
- }
231
- async disconnect() {
232
- this.gmail = null;
233
- this.auth = null;
234
- this.connected = false;
235
- this.debug("Disconnected from Gmail API");
236
- }
237
- // ========================================================================
238
- // Send operations
239
- // ========================================================================
240
- async send(message, options) {
241
- this.ensureConnected();
242
- this.validateMessage(message);
243
- try {
244
- const email = this.buildRFC2822Message(message, options);
245
- const encodedEmail = Buffer.from(email).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
246
- const response = await this.gmail?.users.messages.send({
247
- userId: this.getUserId(),
248
- requestBody: {
249
- raw: encodedEmail
250
- }
251
- });
252
- if (!response) {
253
- throw new SendError("Failed to send message", [], [], "gmail");
254
- }
255
- return {
256
- messageId: response.data.id || "",
257
- accepted: message.to.map((addr) => addr.address),
258
- rejected: [],
259
- response: `Message sent: ${response.data.id}`
260
- };
261
- } catch (error) {
262
- throw this.mapGmailError(error);
263
- }
264
- }
265
- // ========================================================================
266
- // Receive operations
267
- // ========================================================================
268
- async fetch(options) {
269
- this.ensureConnected();
270
- try {
271
- const query = this.buildGmailQuery(options);
272
- let labelIds = options?.labelIds;
273
- if (options?.folder && !labelIds) {
274
- labelIds = [options.folder];
275
- }
276
- let listResponse;
277
- try {
278
- listResponse = await this.gmail?.users.messages.list({
279
- userId: this.getUserId(),
280
- q: query,
281
- labelIds,
282
- maxResults: options?.maxResults || options?.limit || 100
283
- });
284
- } catch (error) {
285
- if (error instanceof Error && error.message.includes("Invalid label")) {
286
- return [];
287
- }
288
- throw error;
289
- }
290
- if (!listResponse) {
291
- return [];
292
- }
293
- const messageIds = listResponse.data.messages || [];
294
- if (messageIds.length === 0) {
295
- return [];
296
- }
297
- const messages = [];
298
- for (const msgId of messageIds) {
299
- if (!msgId.id) continue;
300
- try {
301
- const message = await this.getMessage(msgId.id);
302
- messages.push(message);
303
- if (options?.limit && messages.length >= options.limit) {
304
- break;
305
- }
306
- } catch (error) {
307
- this.logger.error(`Failed to fetch message ${msgId.id}:`, {
308
- error: error instanceof Error ? error.message : String(error)
309
- });
310
- }
311
- }
312
- return messages;
313
- } catch (error) {
314
- throw this.mapGmailError(error);
315
- }
316
- }
317
- async getMessage(messageId) {
318
- this.ensureConnected();
319
- try {
320
- const response = await this.gmail?.users.messages.get({
321
- userId: this.getUserId(),
322
- id: messageId,
323
- format: "raw"
324
- });
325
- if (!response || !response.data.raw) {
326
- throw new MessageNotFoundError(messageId, "gmail");
327
- }
328
- const rawMessage = Buffer.from(response.data.raw, "base64").toString(
329
- "utf-8"
330
- );
331
- const parsed = await simpleParser(rawMessage);
332
- const labels = response.data.labelIds || [];
333
- const extractAddresses = (addressObj) => {
334
- if (!addressObj) return [];
335
- const addressArray = Array.isArray(addressObj) ? addressObj : [addressObj];
336
- const addresses = addressArray.flatMap(
337
- (obj) => Array.isArray(obj.value) ? obj.value : [obj.value]
338
- );
339
- return addresses.map((addr) => ({
340
- address: addr.address || "",
341
- name: addr.name
342
- }));
343
- };
344
- const fromAddresses = extractAddresses(parsed.from);
345
- const toAddresses = extractAddresses(parsed.to);
346
- const ccAddresses = extractAddresses(parsed.cc);
347
- const bccAddresses = extractAddresses(parsed.bcc);
348
- const replyToAddresses = extractAddresses(parsed.replyTo);
349
- return {
350
- id: response.data.id || void 0,
351
- messageId: parsed.messageId || response.data.id || void 0,
352
- threadId: response.data.threadId || void 0,
353
- inReplyTo: parsed.inReplyTo,
354
- references: parsed.references ? Array.isArray(parsed.references) ? parsed.references : [parsed.references] : void 0,
355
- from: fromAddresses[0] || { address: "", name: void 0 },
356
- to: toAddresses,
357
- cc: ccAddresses.length > 0 ? ccAddresses : void 0,
358
- bcc: bccAddresses.length > 0 ? bccAddresses : void 0,
359
- replyTo: replyToAddresses[0],
360
- subject: parsed.subject || "",
361
- date: parsed.date,
362
- text: parsed.text,
363
- html: parsed.html ? parsed.html.toString() : void 0,
364
- attachments: parsed.attachments?.map((att) => ({
365
- filename: att.filename,
366
- contentType: att.contentType,
367
- size: att.size,
368
- content: att.content,
369
- contentId: att.contentId,
370
- contentDisposition: att.contentDisposition
371
- })),
372
- labels,
373
- headers: parsed.headers,
374
- size: Number(response.data.sizeEstimate),
375
- raw: rawMessage
376
- };
377
- } catch (error) {
378
- if (error instanceof MessageNotFoundError) {
379
- throw error;
380
- }
381
- throw this.mapGmailError(error);
382
- }
383
- }
384
- // ========================================================================
385
- // Label operations (Gmail uses labels instead of folders)
386
- // ========================================================================
387
- async listFolders() {
388
- this.ensureConnected();
389
- try {
390
- const response = await this.gmail?.users.labels.list({
391
- userId: this.getUserId()
392
- });
393
- if (!response) {
394
- return [];
395
- }
396
- const labels = response.data.labels || [];
397
- return labels.map((label) => ({
398
- name: label.name || "",
399
- path: label.id || "",
400
- delimiter: "/",
401
- specialUse: this.mapGmailLabelToSpecialUse(label.type),
402
- messageCount: label.messagesTotal || void 0,
403
- unreadCount: label.messagesUnread || void 0
404
- }));
405
- } catch (error) {
406
- throw this.mapGmailError(error);
407
- }
408
- }
409
- async selectFolder(name) {
410
- this.ensureConnected();
411
- try {
412
- const labels = await this.listFolders();
413
- const label = labels.find((l) => l.name === name || l.path === name);
414
- if (!label) {
415
- throw new FolderNotFoundError(name, "gmail");
416
- }
417
- return {
418
- name: label.name,
419
- exists: label.messageCount || 0,
420
- recent: 0,
421
- unseen: label.unreadCount || 0,
422
- uidValidity: 0,
423
- uidNext: 0,
424
- flags: [],
425
- permanentFlags: []
426
- };
427
- } catch (error) {
428
- if (error instanceof FolderNotFoundError) {
429
- throw error;
430
- }
431
- throw this.mapGmailError(error);
432
- }
433
- }
434
- async createFolder(name) {
435
- this.ensureConnected();
436
- try {
437
- const labels = await this.listFolders();
438
- if (labels.some((l) => l.name === name)) {
439
- throw new FolderExistsError(name, "gmail");
440
- }
441
- await this.gmail?.users.labels.create({
442
- userId: this.getUserId(),
443
- requestBody: {
444
- name,
445
- labelListVisibility: "labelShow",
446
- messageListVisibility: "show"
447
- }
448
- });
449
- } catch (error) {
450
- if (error instanceof FolderExistsError) {
451
- throw error;
452
- }
453
- throw this.mapGmailError(error);
454
- }
455
- }
456
- async deleteFolder(name) {
457
- this.ensureConnected();
458
- try {
459
- const labels = await this.listFolders();
460
- const label = labels.find((l) => l.name === name);
461
- if (!label) {
462
- throw new FolderNotFoundError(name, "gmail");
463
- }
464
- if (label.specialUse) {
465
- throw new EmailError(
466
- `Cannot delete system label: ${name}`,
467
- "INVALID_OPERATION",
468
- "gmail"
469
- );
470
- }
471
- await this.gmail?.users.labels.delete({
472
- userId: this.getUserId(),
473
- id: label.path
474
- });
475
- } catch (error) {
476
- if (error instanceof FolderNotFoundError || error instanceof EmailError) {
477
- throw error;
478
- }
479
- throw this.mapGmailError(error);
480
- }
481
- }
482
- // ========================================================================
483
- // Message operations
484
- // ========================================================================
485
- async markRead(messageId) {
486
- this.ensureConnected();
487
- const ids = this.normalizeMessageIds(messageId);
488
- try {
489
- for (const id of ids) {
490
- await this.gmail?.users.messages.modify({
491
- userId: this.getUserId(),
492
- id,
493
- requestBody: {
494
- removeLabelIds: ["UNREAD"]
495
- }
496
- });
497
- }
498
- } catch (error) {
499
- throw this.mapGmailError(error);
500
- }
501
- }
502
- async markUnread(messageId) {
503
- this.ensureConnected();
504
- const ids = this.normalizeMessageIds(messageId);
505
- try {
506
- for (const id of ids) {
507
- await this.gmail?.users.messages.modify({
508
- userId: this.getUserId(),
509
- id,
510
- requestBody: {
511
- addLabelIds: ["UNREAD"]
512
- }
513
- });
514
- }
515
- } catch (error) {
516
- throw this.mapGmailError(error);
517
- }
518
- }
519
- async move(messageId, folder) {
520
- this.ensureConnected();
521
- const ids = this.normalizeMessageIds(messageId);
522
- try {
523
- const labels = await this.listFolders();
524
- const label = labels.find((l) => l.name === folder || l.path === folder);
525
- if (!label) {
526
- throw new FolderNotFoundError(folder, "gmail");
527
- }
528
- for (const id of ids) {
529
- await this.gmail?.users.messages.modify({
530
- userId: this.getUserId(),
531
- id,
532
- requestBody: {
533
- addLabelIds: [label.path]
534
- }
535
- });
536
- }
537
- } catch (error) {
538
- if (error instanceof FolderNotFoundError) {
539
- throw error;
540
- }
541
- throw this.mapGmailError(error);
542
- }
543
- }
544
- async copy(messageId, folder) {
545
- await this.move(messageId, folder);
546
- }
547
- async delete(messageId) {
548
- this.ensureConnected();
549
- const ids = this.normalizeMessageIds(messageId);
550
- try {
551
- for (const id of ids) {
552
- await this.gmail?.users.messages.trash({
553
- userId: this.getUserId(),
554
- id
555
- });
556
- }
557
- } catch (error) {
558
- throw this.mapGmailError(error);
559
- }
560
- }
561
- // ========================================================================
562
- // Search
563
- // ========================================================================
564
- async search(criteria) {
565
- this.ensureConnected();
566
- try {
567
- if (criteria.q) {
568
- return await this.fetch({ q: criteria.q });
569
- }
570
- const queryParts = [];
571
- if (criteria.from) queryParts.push(`from:${criteria.from}`);
572
- if (criteria.to) queryParts.push(`to:${criteria.to}`);
573
- if (criteria.subject) queryParts.push(`subject:${criteria.subject}`);
574
- if (criteria.body) queryParts.push(criteria.body);
575
- if (criteria.since) {
576
- queryParts.push(`after:${this.formatGmailDate(criteria.since)}`);
577
- }
578
- if (criteria.before) {
579
- queryParts.push(`before:${this.formatGmailDate(criteria.before)}`);
580
- }
581
- if (criteria.unread === true) queryParts.push("is:unread");
582
- if (criteria.unread === false) queryParts.push("is:read");
583
- if (criteria.flagged) queryParts.push("is:starred");
584
- if (criteria.larger) queryParts.push(`larger:${criteria.larger}`);
585
- if (criteria.smaller) queryParts.push(`smaller:${criteria.smaller}`);
586
- const query = queryParts.join(" ");
587
- return await this.fetch({ q: query });
588
- } catch (error) {
589
- throw this.mapGmailError(error);
590
- }
591
- }
592
- // ========================================================================
593
- // Adapter info
594
- // ========================================================================
595
- async getCapabilities() {
596
- return {
597
- send: true,
598
- receive: true,
599
- folders: true,
600
- search: true,
601
- markRead: true,
602
- move: true,
603
- delete: true,
604
- threads: true,
605
- oauth: true
606
- };
607
- }
608
- getAdapter() {
609
- return "gmail";
610
- }
611
- // ========================================================================
612
- // Private helper methods
613
- // ========================================================================
614
- ensureConnected() {
615
- if (!this.connected || !this.gmail) {
616
- throw new ConnectionError(
617
- "Not connected to Gmail. Call connect() first.",
618
- "gmail"
619
- );
620
- }
621
- }
622
- getUserId() {
623
- return this.options.userId || "me";
624
- }
625
- buildRFC2822Message(message, options) {
626
- const lines = [];
627
- const hasAttachments = message.attachments && message.attachments.length > 0;
628
- lines.push(`From: ${this.formatEmailAddress(message.from)}`);
629
- lines.push(
630
- `To: ${message.to.map((a) => this.formatEmailAddress(a)).join(", ")}`
631
- );
632
- if (message.cc && message.cc.length > 0) {
633
- lines.push(
634
- `Cc: ${message.cc.map((a) => this.formatEmailAddress(a)).join(", ")}`
635
- );
636
- }
637
- if (message.replyTo) {
638
- lines.push(`Reply-To: ${this.formatEmailAddress(message.replyTo)}`);
639
- }
640
- lines.push(`Subject: ${message.subject}`);
641
- lines.push(
642
- `Date: ${options?.date?.toUTCString() || (/* @__PURE__ */ new Date()).toUTCString()}`
643
- );
644
- if (options?.messageId) {
645
- lines.push(`Message-ID: <${options.messageId}>`);
646
- }
647
- lines.push("MIME-Version: 1.0");
648
- if (hasAttachments) {
649
- lines.push('Content-Type: multipart/mixed; boundary="mixed-boundary"');
650
- lines.push("");
651
- lines.push("--mixed-boundary");
652
- if (message.html) {
653
- lines.push(
654
- 'Content-Type: multipart/alternative; boundary="alt-boundary"'
655
- );
656
- lines.push("");
657
- lines.push("--alt-boundary");
658
- lines.push('Content-Type: text/plain; charset="UTF-8"');
659
- lines.push("Content-Transfer-Encoding: 7bit");
660
- lines.push("");
661
- lines.push(message.text || "");
662
- lines.push("");
663
- lines.push("--alt-boundary");
664
- lines.push('Content-Type: text/html; charset="UTF-8"');
665
- lines.push("Content-Transfer-Encoding: 7bit");
666
- lines.push("");
667
- lines.push(message.html);
668
- lines.push("");
669
- lines.push("--alt-boundary--");
670
- } else {
671
- lines.push('Content-Type: text/plain; charset="UTF-8"');
672
- lines.push("Content-Transfer-Encoding: 7bit");
673
- lines.push("");
674
- lines.push(message.text || "");
675
- }
676
- if (message.attachments) {
677
- for (const attachment of message.attachments) {
678
- lines.push("");
679
- lines.push("--mixed-boundary");
680
- lines.push(
681
- `Content-Type: ${attachment.contentType}; name="${attachment.filename}"`
682
- );
683
- lines.push("Content-Transfer-Encoding: base64");
684
- lines.push(
685
- `Content-Disposition: ${attachment.contentDisposition || "attachment"}; filename="${attachment.filename}"`
686
- );
687
- if (attachment.contentId) {
688
- lines.push(`Content-ID: <${attachment.contentId}>`);
689
- }
690
- lines.push("");
691
- const content = attachment.content || Buffer.from("");
692
- const base64Content = content.toString("base64");
693
- const base64Lines = base64Content.match(/.{1,76}/g) || [];
694
- lines.push(...base64Lines);
695
- }
696
- }
697
- lines.push("");
698
- lines.push("--mixed-boundary--");
699
- } else {
700
- if (message.html) {
701
- lines.push(
702
- 'Content-Type: multipart/alternative; boundary="alt-boundary"'
703
- );
704
- lines.push("");
705
- lines.push("--alt-boundary");
706
- lines.push('Content-Type: text/plain; charset="UTF-8"');
707
- lines.push("Content-Transfer-Encoding: 7bit");
708
- lines.push("");
709
- lines.push(message.text || "");
710
- lines.push("");
711
- lines.push("--alt-boundary");
712
- lines.push('Content-Type: text/html; charset="UTF-8"');
713
- lines.push("Content-Transfer-Encoding: 7bit");
714
- lines.push("");
715
- lines.push(message.html);
716
- lines.push("");
717
- lines.push("--alt-boundary--");
718
- } else {
719
- lines.push('Content-Type: text/plain; charset="UTF-8"');
720
- lines.push("Content-Transfer-Encoding: 7bit");
721
- lines.push("");
722
- lines.push(message.text || "");
723
- }
724
- }
725
- return lines.join("\r\n");
726
- }
727
- formatEmailAddress(addr) {
728
- if (addr.name) {
729
- return `${addr.name} <${addr.address}>`;
730
- }
731
- return addr.address;
732
- }
733
- buildGmailQuery(options) {
734
- const queryParts = [];
735
- if (options?.q) {
736
- return options.q;
737
- }
738
- if (options?.since) {
739
- queryParts.push(`after:${this.formatGmailDate(options.since)}`);
740
- }
741
- if (options?.before) {
742
- queryParts.push(`before:${this.formatGmailDate(options.before)}`);
743
- }
744
- if (options?.unreadOnly) {
745
- queryParts.push("is:unread");
746
- }
747
- return queryParts.join(" ");
748
- }
749
- formatGmailDate(date) {
750
- const year = date.getFullYear();
751
- const month = String(date.getMonth() + 1).padStart(2, "0");
752
- const day = String(date.getDate()).padStart(2, "0");
753
- return `${year}/${month}/${day}`;
754
- }
755
- mapGmailLabelToSpecialUse(type) {
756
- switch (type) {
757
- case "system":
758
- return "\\Inbox";
759
- default:
760
- return void 0;
761
- }
762
- }
763
- mapGmailError(error) {
764
- if (error instanceof EmailError) {
765
- return error;
766
- }
767
- if (error instanceof Error) {
768
- const message = error.message.toLowerCase();
769
- const errorObj = error;
770
- if (message.includes("timeout") || errorObj.code === "ETIMEDOUT" || errorObj.code === "ESOCKETTIMEDOUT") {
771
- return new TimeoutError(error.message, "gmail", error);
772
- }
773
- if (message.includes("network") || message.includes("connect") || errorObj.code === "ECONNREFUSED" || errorObj.code === "ENOTFOUND" || errorObj.code === "ECONNRESET") {
774
- return new ConnectionError(error.message, "gmail", error);
775
- }
776
- if (message.includes("auth") || message.includes("invalid_grant") || message.includes("unauthorized") || errorObj.code === 401) {
777
- return new AuthenticationError(error.message, "gmail", error);
778
- }
779
- if (message.includes("recipient") || message.includes("invalid email")) {
780
- return new SendError(error.message, [], [], "gmail", error);
781
- }
782
- return new EmailError(error.message, "GMAIL_ERROR", "gmail", error);
783
- }
784
- return new EmailError(String(error), "GMAIL_ERROR", "gmail");
785
- }
786
- }
787
- const gmail = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
788
- __proto__: null,
789
- GmailAdapter
790
- }, Symbol.toStringTag, { value: "Module" }));
791
- class IMAPAdapter extends BaseEmailClient {
792
- client = null;
793
- options;
794
- currentFolder = null;
795
- constructor(options) {
796
- super({
797
- type: "imap",
798
- debug: options.debug
799
- });
800
- this.options = options;
801
- }
802
- /**
803
- * Create ImapFlow client
804
- */
805
- createClient() {
806
- const config = {
807
- host: this.options.host,
808
- port: this.options.port,
809
- secure: this.options.secure ?? true,
810
- auth: this.options.auth,
811
- tls: this.options.tls,
812
- logger: this.options.debug ? {
813
- debug: (msg) => this.debug(msg),
814
- info: (msg) => this.debug(msg),
815
- warn: (msg) => this.debug(`WARN: ${msg}`),
816
- error: (msg) => this.debug(`ERROR: ${msg}`)
817
- } : false
818
- };
819
- return new ImapFlow(config);
820
- }
821
- getAdapter() {
822
- return "imap";
823
- }
824
- /**
825
- * Helper to convert search results to array
826
- */
827
- searchResultToArray(result) {
828
- if (!result || !Array.isArray(result)) {
829
- return [];
830
- }
831
- return result;
832
- }
833
- /**
834
- * Connect to IMAP server
835
- */
836
- async connect() {
837
- try {
838
- this.debug("Connecting to IMAP server", { host: this.options.host });
839
- this.client = this.createClient();
840
- await this.client.connect();
841
- this.connected = true;
842
- this.debug("IMAP connection established");
843
- } catch (error) {
844
- this.connected = false;
845
- throw this.mapIMAPError(error);
846
- }
847
- }
848
- /**
849
- * Disconnect from IMAP server
850
- */
851
- async disconnect() {
852
- if (this.client) {
853
- await this.client.logout();
854
- this.client = null;
855
- this.connected = false;
856
- this.currentFolder = null;
857
- this.debug("IMAP connection closed");
858
- }
859
- }
860
- /**
861
- * Fetch messages with filters
862
- */
863
- async fetch(options) {
864
- this.ensureConnected();
865
- const folder = options?.folder || "INBOX";
866
- await this.selectFolder(folder);
867
- try {
868
- const searchQuery = this.buildSearchQuery(options);
869
- this.debug("Fetching messages", { folder, query: searchQuery });
870
- const searchResult = await this.client.search(searchQuery, {
871
- uid: true
872
- });
873
- const uids = this.searchResultToArray(searchResult);
874
- let targetUids = uids;
875
- if (options?.offset || options?.limit) {
876
- const start = options.offset || 0;
877
- const end = options.limit ? start + options.limit : void 0;
878
- targetUids = uids.slice(start, end);
879
- }
880
- if (targetUids.length === 0) {
881
- return [];
882
- }
883
- const messages = [];
884
- for await (const msg of this.client.fetch(targetUids, {
885
- source: true,
886
- flags: true,
887
- uid: true,
888
- envelope: true,
889
- bodyStructure: true
890
- })) {
891
- try {
892
- if (!msg.source) {
893
- this.debug("Message has no source", { uid: msg.uid });
894
- continue;
895
- }
896
- const parsed = await simpleParser(msg.source);
897
- const email = this.parseMessage(parsed, msg);
898
- messages.push(email);
899
- if (options?.markSeen) {
900
- await this.client.messageFlagsAdd(msg.uid, ["\\Seen"], {
901
- uid: true
902
- });
903
- }
904
- } catch (parseError) {
905
- this.debug("Failed to parse message", { uid: msg.uid, parseError });
906
- }
907
- }
908
- return messages;
909
- } catch (error) {
910
- throw this.mapIMAPError(error);
911
- }
912
- }
913
- /**
914
- * Get a specific message by ID
915
- */
916
- async getMessage(messageId) {
917
- this.ensureConnected();
918
- try {
919
- const searchResult = await this.client.search(
920
- { header: ["message-id", messageId] },
921
- { uid: true }
922
- );
923
- const uids = this.searchResultToArray(searchResult);
924
- if (uids.length === 0) {
925
- throw new MessageNotFoundError(messageId, "imap");
926
- }
927
- const messages = [];
928
- for await (const msg of this.client.fetch(uids[0], {
929
- source: true,
930
- flags: true,
931
- uid: true
932
- })) {
933
- if (!msg.source) {
934
- continue;
935
- }
936
- const parsed = await simpleParser(msg.source);
937
- messages.push(this.parseMessage(parsed, msg));
938
- }
939
- if (messages.length === 0) {
940
- throw new MessageNotFoundError(messageId, "imap");
941
- }
942
- return messages[0];
943
- } catch (error) {
944
- if (error instanceof EmailError) {
945
- throw error;
946
- }
947
- throw this.mapIMAPError(error);
948
- }
949
- }
950
- /**
951
- * List all folders
952
- */
953
- async listFolders() {
954
- this.ensureConnected();
955
- try {
956
- const list = await this.client?.list();
957
- if (!list) {
958
- return [];
959
- }
960
- return list.map((folder) => ({
961
- name: folder.name,
962
- path: folder.path,
963
- delimiter: folder.delimiter,
964
- specialUse: folder.specialUse,
965
- subscribed: folder.subscribed
966
- }));
967
- } catch (error) {
968
- throw this.mapIMAPError(error);
969
- }
970
- }
971
- /**
972
- * Select a folder
973
- */
974
- async selectFolder(name) {
975
- this.ensureConnected();
976
- try {
977
- const mailbox = await this.client?.mailboxOpen(name);
978
- this.currentFolder = name;
979
- if (!mailbox) {
980
- throw new FolderNotFoundError(name, "imap");
981
- }
982
- return {
983
- name,
984
- exists: Number(mailbox.exists),
985
- recent: 0,
986
- // ImapFlow doesn't provide this
987
- unseen: 0,
988
- // Would need separate STATUS command
989
- uidValidity: Number(mailbox.uidValidity),
990
- uidNext: Number(mailbox.uidNext),
991
- flags: Array.from(mailbox.flags || []),
992
- permanentFlags: Array.from(mailbox.permanentFlags || [])
993
- };
994
- } catch (error) {
995
- if (error instanceof Error && error.message.toLowerCase().includes("does not exist")) {
996
- throw new FolderNotFoundError(name, "imap");
997
- }
998
- throw this.mapIMAPError(error);
999
- }
1000
- }
1001
- /**
1002
- * Create a new folder
1003
- */
1004
- async createFolder(name) {
1005
- this.ensureConnected();
1006
- try {
1007
- await this.client?.mailboxCreate(name);
1008
- this.debug("Folder created", { name });
1009
- } catch (error) {
1010
- if (error instanceof Error && error.message.toLowerCase().includes("already exists")) {
1011
- throw new FolderExistsError(name, "imap");
1012
- }
1013
- throw this.mapIMAPError(error);
1014
- }
1015
- }
1016
- /**
1017
- * Delete a folder
1018
- */
1019
- async deleteFolder(name) {
1020
- this.ensureConnected();
1021
- try {
1022
- await this.client?.mailboxDelete(name);
1023
- this.debug("Folder deleted", { name });
1024
- } catch (error) {
1025
- if (error instanceof Error && error.message.toLowerCase().includes("does not exist")) {
1026
- throw new FolderNotFoundError(name, "imap");
1027
- }
1028
- throw this.mapIMAPError(error);
1029
- }
1030
- }
1031
- /**
1032
- * Mark message(s) as read
1033
- */
1034
- async markRead(messageId) {
1035
- this.ensureConnected();
1036
- const ids = Array.isArray(messageId) ? messageId : [messageId];
1037
- try {
1038
- for (const id of ids) {
1039
- const searchResult = await this.client?.search(
1040
- { header: ["message-id", id] },
1041
- { uid: true }
1042
- );
1043
- const uids = this.searchResultToArray(searchResult);
1044
- if (uids.length > 0) {
1045
- await this.client?.messageFlagsAdd(uids[0], ["\\Seen"], {
1046
- uid: true
1047
- });
1048
- }
1049
- }
1050
- } catch (error) {
1051
- throw this.mapIMAPError(error);
1052
- }
1053
- }
1054
- /**
1055
- * Mark message(s) as unread
1056
- */
1057
- async markUnread(messageId) {
1058
- this.ensureConnected();
1059
- const ids = Array.isArray(messageId) ? messageId : [messageId];
1060
- try {
1061
- for (const id of ids) {
1062
- const searchResult = await this.client?.search(
1063
- { header: ["message-id", id] },
1064
- { uid: true }
1065
- );
1066
- const uids = this.searchResultToArray(searchResult);
1067
- if (uids.length > 0) {
1068
- await this.client?.messageFlagsRemove(uids[0], ["\\Seen"], {
1069
- uid: true
1070
- });
1071
- }
1072
- }
1073
- } catch (error) {
1074
- throw this.mapIMAPError(error);
1075
- }
1076
- }
1077
- /**
1078
- * Move message(s) to another folder
1079
- */
1080
- async move(messageId, folder) {
1081
- this.ensureConnected();
1082
- const ids = Array.isArray(messageId) ? messageId : [messageId];
1083
- try {
1084
- for (const id of ids) {
1085
- const searchResult = await this.client?.search(
1086
- { header: ["message-id", id] },
1087
- { uid: true }
1088
- );
1089
- const uids = this.searchResultToArray(searchResult);
1090
- if (uids.length > 0) {
1091
- await this.client?.messageMove(uids[0], folder, { uid: true });
1092
- }
1093
- }
1094
- } catch (error) {
1095
- throw this.mapIMAPError(error);
1096
- }
1097
- }
1098
- /**
1099
- * Copy message(s) to another folder
1100
- */
1101
- async copy(messageId, folder) {
1102
- this.ensureConnected();
1103
- const ids = Array.isArray(messageId) ? messageId : [messageId];
1104
- try {
1105
- for (const id of ids) {
1106
- const searchResult = await this.client?.search(
1107
- { header: ["message-id", id] },
1108
- { uid: true }
1109
- );
1110
- const uids = this.searchResultToArray(searchResult);
1111
- if (uids.length > 0) {
1112
- await this.client?.messageCopy(uids[0], folder, { uid: true });
1113
- }
1114
- }
1115
- } catch (error) {
1116
- throw this.mapIMAPError(error);
1117
- }
1118
- }
1119
- /**
1120
- * Delete message(s)
1121
- */
1122
- async delete(messageId) {
1123
- this.ensureConnected();
1124
- const ids = Array.isArray(messageId) ? messageId : [messageId];
1125
- try {
1126
- for (const id of ids) {
1127
- const searchResult = await this.client?.search(
1128
- { header: ["message-id", id] },
1129
- { uid: true }
1130
- );
1131
- const uids = this.searchResultToArray(searchResult);
1132
- if (uids.length > 0) {
1133
- await this.client?.messageFlagsAdd(uids[0], ["\\Deleted"], {
1134
- uid: true
1135
- });
1136
- }
1137
- }
1138
- } catch (error) {
1139
- throw this.mapIMAPError(error);
1140
- }
1141
- }
1142
- /**
1143
- * Search messages
1144
- */
1145
- async search(criteria) {
1146
- this.ensureConnected();
1147
- try {
1148
- const query = this.buildSearchCriteria(criteria);
1149
- const searchResult = await this.client.search(query, { uid: true });
1150
- const uids = this.searchResultToArray(searchResult);
1151
- if (uids.length === 0) {
1152
- return [];
1153
- }
1154
- const messages = [];
1155
- for await (const msg of this.client.fetch(uids, {
1156
- source: true,
1157
- flags: true,
1158
- uid: true
1159
- })) {
1160
- if (!msg.source) {
1161
- continue;
1162
- }
1163
- const parsed = await simpleParser(msg.source);
1164
- messages.push(this.parseMessage(parsed, msg));
1165
- }
1166
- return messages;
1167
- } catch (error) {
1168
- throw this.mapIMAPError(error);
1169
- }
1170
- }
1171
- /**
1172
- * Get adapter capabilities
1173
- */
1174
- async getCapabilities() {
1175
- return {
1176
- send: false,
1177
- // IMAP is receive-only
1178
- receive: true,
1179
- folders: true,
1180
- search: true,
1181
- markRead: true,
1182
- move: true,
1183
- delete: true,
1184
- threads: false,
1185
- // Thread support not implemented yet
1186
- oauth: this.isOAuth2()
1187
- };
1188
- }
1189
- /**
1190
- * Check if using OAuth2 authentication
1191
- */
1192
- isOAuth2() {
1193
- return this.options.auth !== void 0 && "type" in this.options.auth && this.options.auth.type === "OAuth2";
1194
- }
1195
- // ========================================================================
1196
- // Unsupported operations (IMAP is receive-only)
1197
- // ========================================================================
1198
- async send(_message, _options) {
1199
- throw new EmailError(
1200
- "IMAP does not support sending messages. Use SMTP adapter.",
1201
- "UNSUPPORTED_OPERATION",
1202
- "imap"
1203
- );
1204
- }
1205
- // ========================================================================
1206
- // Helper methods
1207
- // ========================================================================
1208
- /**
1209
- * Ensure client is connected
1210
- */
1211
- ensureConnected() {
1212
- if (!this.client || !this.connected) {
1213
- throw new ConnectionError(
1214
- "Not connected to IMAP server. Call connect() first.",
1215
- "imap"
1216
- );
1217
- }
1218
- }
1219
- /**
1220
- * Build IMAP search query from FetchOptions
1221
- */
1222
- buildSearchQuery(options) {
1223
- const query = {};
1224
- if (options?.unreadOnly) {
1225
- query.unseen = true;
1226
- }
1227
- if (options?.since) {
1228
- query.since = options.since;
1229
- }
1230
- if (options?.before) {
1231
- query.before = options.before;
1232
- }
1233
- if (Object.keys(query).length === 0) {
1234
- query.all = true;
1235
- }
1236
- return query;
1237
- }
1238
- /**
1239
- * Build IMAP search criteria from SearchCriteria
1240
- */
1241
- buildSearchCriteria(criteria) {
1242
- const query = {};
1243
- if (criteria.from) {
1244
- query.from = criteria.from;
1245
- }
1246
- if (criteria.to) {
1247
- query.to = criteria.to;
1248
- }
1249
- if (criteria.subject) {
1250
- query.subject = criteria.subject;
1251
- }
1252
- if (criteria.body) {
1253
- query.body = criteria.body;
1254
- }
1255
- if (criteria.since) {
1256
- query.since = criteria.since;
1257
- }
1258
- if (criteria.before) {
1259
- query.before = criteria.before;
1260
- }
1261
- if (criteria.unread) {
1262
- query.unseen = true;
1263
- }
1264
- if (criteria.flagged) {
1265
- query.flagged = true;
1266
- }
1267
- if (criteria.answered) {
1268
- query.answered = true;
1269
- }
1270
- if (criteria.draft) {
1271
- query.draft = true;
1272
- }
1273
- if (criteria.deleted) {
1274
- query.deleted = true;
1275
- }
1276
- if (criteria.larger) {
1277
- query.larger = criteria.larger;
1278
- }
1279
- if (criteria.smaller) {
1280
- query.smaller = criteria.smaller;
1281
- }
1282
- if (criteria.header) {
1283
- query.header = Object.entries(criteria.header).map(([key, value]) => [
1284
- key,
1285
- value
1286
- ]);
1287
- }
1288
- if (Object.keys(query).length === 0) {
1289
- query.all = true;
1290
- }
1291
- return query;
1292
- }
1293
- /**
1294
- * Parse mailparser output to EmailMessage
1295
- */
1296
- parseMessage(parsed, msg) {
1297
- const extractAddresses = (addressObj) => {
1298
- if (!addressObj) return [];
1299
- const addressArray = Array.isArray(addressObj) ? addressObj : [addressObj];
1300
- const addresses = addressArray.flatMap(
1301
- (obj) => Array.isArray(obj.value) ? obj.value : [obj.value]
1302
- );
1303
- return addresses.map((addr) => ({
1304
- address: addr.address || "",
1305
- name: addr.name
1306
- }));
1307
- };
1308
- const fromAddresses = extractAddresses(parsed.from);
1309
- const toAddresses = extractAddresses(parsed.to);
1310
- const ccAddresses = extractAddresses(parsed.cc);
1311
- const bccAddresses = extractAddresses(parsed.bcc);
1312
- const replyToAddresses = extractAddresses(parsed.replyTo);
1313
- return {
1314
- id: String(msg.uid),
1315
- messageId: parsed.messageId || void 0,
1316
- from: fromAddresses[0] || { address: "", name: void 0 },
1317
- to: toAddresses,
1318
- cc: ccAddresses.length > 0 ? ccAddresses : void 0,
1319
- bcc: bccAddresses.length > 0 ? bccAddresses : void 0,
1320
- replyTo: replyToAddresses[0],
1321
- subject: parsed.subject || "",
1322
- date: parsed.date,
1323
- text: parsed.text,
1324
- html: parsed.html ? String(parsed.html) : void 0,
1325
- attachments: parsed.attachments?.map((att) => ({
1326
- filename: att.filename,
1327
- contentType: att.contentType,
1328
- size: att.size,
1329
- content: att.content,
1330
- contentId: att.contentId,
1331
- contentDisposition: att.contentDisposition
1332
- })),
1333
- flags: msg.flags ? Array.from(msg.flags) : void 0,
1334
- inReplyTo: parsed.inReplyTo,
1335
- references: Array.isArray(parsed.references) ? parsed.references : parsed.references ? [parsed.references] : void 0,
1336
- headers: parsed.headers ? Object.fromEntries(
1337
- Array.from(parsed.headers.entries()).map(([key, value]) => [
1338
- key,
1339
- Array.isArray(value) ? value : String(value)
1340
- ])
1341
- ) : void 0
1342
- };
1343
- }
1344
- /**
1345
- * Map ImapFlow errors to standard error types
1346
- */
1347
- mapIMAPError(error) {
1348
- if (error instanceof EmailError) {
1349
- return error;
1350
- }
1351
- if (error instanceof Error) {
1352
- const message = error.message.toLowerCase();
1353
- if (message.includes("authentication") || message.includes("auth") || message.includes("login") || message.includes("invalid credentials")) {
1354
- return new AuthenticationError(error.message, "imap", error);
1355
- }
1356
- if (message.includes("timeout") || message.includes("etimedout")) {
1357
- return new TimeoutError(error.message, "imap", error);
1358
- }
1359
- if (message.includes("econnrefused") || message.includes("enotfound") || message.includes("connection") || message.includes("ehostunreach") || message.includes("network")) {
1360
- return new ConnectionError(error.message, "imap", error);
1361
- }
1362
- return new EmailError(error.message, "IMAP_ERROR", "imap", error);
1363
- }
1364
- return new EmailError(String(error), "UNKNOWN_ERROR", "imap");
1365
- }
1366
- }
1367
- const imap = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1368
- __proto__: null,
1369
- IMAPAdapter
1370
- }, Symbol.toStringTag, { value: "Module" }));
1371
- class POP3Adapter extends BaseEmailClient {
1372
- client = null;
1373
- options;
1374
- messageCache = /* @__PURE__ */ new Map();
1375
- // msgNum -> UID
1376
- constructor(options) {
1377
- super({
1378
- type: "pop3",
1379
- debug: options.debug
1380
- });
1381
- this.options = options;
1382
- }
1383
- // ========================================================================
1384
- // Connection management
1385
- // ========================================================================
1386
- async connect() {
1387
- try {
1388
- this.client = new Pop3Command({
1389
- user: this.options.auth.user,
1390
- password: this.options.auth.pass,
1391
- host: this.options.host,
1392
- port: this.options.port,
1393
- tls: this.options.secure ?? this.options.port === 995,
1394
- timeout: this.options.connectionTimeout,
1395
- tlsOptions: this.options.tls
1396
- });
1397
- await this.client.connect();
1398
- this.connected = true;
1399
- this.debug("Connected to POP3 server");
1400
- } catch (error) {
1401
- throw this.mapPOP3Error(error);
1402
- }
1403
- }
1404
- async disconnect() {
1405
- if (!this.client) {
1406
- return;
1407
- }
1408
- try {
1409
- await this.client.QUIT();
1410
- this.client = null;
1411
- this.connected = false;
1412
- this.messageCache.clear();
1413
- this.debug("Disconnected from POP3 server");
1414
- } catch (error) {
1415
- this.client = null;
1416
- this.connected = false;
1417
- this.messageCache.clear();
1418
- throw this.mapPOP3Error(error);
1419
- }
1420
- }
1421
- // ========================================================================
1422
- // Message operations
1423
- // ========================================================================
1424
- async fetch(options) {
1425
- this.ensureConnected();
1426
- try {
1427
- const uidlResponse = await this.client?.UIDL();
1428
- if (!uidlResponse || !uidlResponse[0]) {
1429
- return [];
1430
- }
1431
- const uidList = this.parseUidlResponse(
1432
- Array.isArray(uidlResponse[0]) ? uidlResponse[0].join("\n") : uidlResponse[0]
1433
- );
1434
- if (uidList.length === 0) {
1435
- return [];
1436
- }
1437
- this.messageCache.clear();
1438
- for (const { msgNum, uid } of uidList) {
1439
- this.messageCache.set(msgNum, uid);
1440
- }
1441
- let messageNums = uidList.map((item) => item.msgNum);
1442
- if (options?.offset) {
1443
- messageNums = messageNums.slice(options.offset);
1444
- }
1445
- if (options?.limit) {
1446
- messageNums = messageNums.slice(0, options.limit);
1447
- }
1448
- const messages = [];
1449
- for (const msgNum of messageNums) {
1450
- try {
1451
- const message = await this.fetchMessage(msgNum);
1452
- if (this.shouldIncludeMessage(message, options)) {
1453
- messages.push(message);
1454
- if (!this.options.leaveOnServer) {
1455
- await this.deleteMessageByNum(msgNum);
1456
- }
1457
- }
1458
- } catch (error) {
1459
- this.logger.error(`Failed to fetch message ${msgNum}:`, {
1460
- error: error instanceof Error ? error.message : String(error)
1461
- });
1462
- }
1463
- }
1464
- return messages;
1465
- } catch (error) {
1466
- throw this.mapPOP3Error(error);
1467
- }
1468
- }
1469
- async getMessage(messageId) {
1470
- this.ensureConnected();
1471
- try {
1472
- let msgNum = null;
1473
- for (const [num, uid] of this.messageCache.entries()) {
1474
- if (uid === messageId) {
1475
- msgNum = num;
1476
- break;
1477
- }
1478
- }
1479
- if (!msgNum) {
1480
- const uidlResponse = await this.client?.UIDL();
1481
- if (!uidlResponse || !uidlResponse[0]) {
1482
- throw new MessageNotFoundError(messageId, "pop3");
1483
- }
1484
- const uidList = this.parseUidlResponse(
1485
- Array.isArray(uidlResponse[0]) ? uidlResponse[0].join("\n") : uidlResponse[0]
1486
- );
1487
- this.messageCache.clear();
1488
- for (const { msgNum: num, uid } of uidList) {
1489
- this.messageCache.set(num, uid);
1490
- if (uid === messageId) {
1491
- msgNum = num;
1492
- }
1493
- }
1494
- }
1495
- if (!msgNum) {
1496
- throw new MessageNotFoundError(messageId, "pop3");
1497
- }
1498
- return await this.fetchMessage(msgNum);
1499
- } catch (error) {
1500
- if (error instanceof MessageNotFoundError) {
1501
- throw error;
1502
- }
1503
- throw this.mapPOP3Error(error);
1504
- }
1505
- }
1506
- async delete(messageId) {
1507
- this.ensureConnected();
1508
- const ids = this.normalizeMessageIds(messageId);
1509
- for (const id of ids) {
1510
- try {
1511
- let msgNum = null;
1512
- for (const [num, uid] of this.messageCache.entries()) {
1513
- if (uid === id) {
1514
- msgNum = num;
1515
- break;
1516
- }
1517
- }
1518
- if (!msgNum) {
1519
- const uidlResponse = await this.client?.UIDL();
1520
- if (!uidlResponse || !uidlResponse[0]) {
1521
- throw new MessageNotFoundError(id, "pop3");
1522
- }
1523
- const uidList = this.parseUidlResponse(
1524
- Array.isArray(uidlResponse[0]) ? uidlResponse[0].join("\n") : uidlResponse[0]
1525
- );
1526
- for (const { msgNum: num, uid } of uidList) {
1527
- if (uid === id) {
1528
- msgNum = num;
1529
- break;
1530
- }
1531
- }
1532
- }
1533
- if (!msgNum) {
1534
- throw new MessageNotFoundError(id, "pop3");
1535
- }
1536
- await this.deleteMessageByNum(msgNum);
1537
- } catch (error) {
1538
- throw this.mapPOP3Error(error);
1539
- }
1540
- }
1541
- }
1542
- // ========================================================================
1543
- // Unsupported operations (POP3 limitations)
1544
- // ========================================================================
1545
- async send(_message, _options) {
1546
- throw new EmailError(
1547
- "POP3 does not support sending email. Use SMTP instead.",
1548
- "UNSUPPORTED_OPERATION",
1549
- "pop3"
1550
- );
1551
- }
1552
- async listFolders() {
1553
- throw new EmailError(
1554
- "POP3 does not support folders",
1555
- "UNSUPPORTED_OPERATION",
1556
- "pop3"
1557
- );
1558
- }
1559
- async selectFolder(_name) {
1560
- throw new EmailError(
1561
- "POP3 does not support folders",
1562
- "UNSUPPORTED_OPERATION",
1563
- "pop3"
1564
- );
1565
- }
1566
- async createFolder(_name) {
1567
- throw new EmailError(
1568
- "POP3 does not support folders",
1569
- "UNSUPPORTED_OPERATION",
1570
- "pop3"
1571
- );
1572
- }
1573
- async deleteFolder(_name) {
1574
- throw new EmailError(
1575
- "POP3 does not support folders",
1576
- "UNSUPPORTED_OPERATION",
1577
- "pop3"
1578
- );
1579
- }
1580
- async markRead(_messageId) {
1581
- throw new EmailError(
1582
- "POP3 does not support marking messages as read",
1583
- "UNSUPPORTED_OPERATION",
1584
- "pop3"
1585
- );
1586
- }
1587
- async markUnread(_messageId) {
1588
- throw new EmailError(
1589
- "POP3 does not support marking messages as unread",
1590
- "UNSUPPORTED_OPERATION",
1591
- "pop3"
1592
- );
1593
- }
1594
- async move(_messageId, _folder) {
1595
- throw new EmailError(
1596
- "POP3 does not support moving messages",
1597
- "UNSUPPORTED_OPERATION",
1598
- "pop3"
1599
- );
1600
- }
1601
- async copy(_messageId, _folder) {
1602
- throw new EmailError(
1603
- "POP3 does not support copying messages",
1604
- "UNSUPPORTED_OPERATION",
1605
- "pop3"
1606
- );
1607
- }
1608
- async search(_criteria) {
1609
- throw new EmailError(
1610
- "POP3 does not support server-side search. Use fetch() with filters instead.",
1611
- "UNSUPPORTED_OPERATION",
1612
- "pop3"
1613
- );
1614
- }
1615
- // ========================================================================
1616
- // Adapter info
1617
- // ========================================================================
1618
- async getCapabilities() {
1619
- return {
1620
- send: false,
1621
- receive: true,
1622
- folders: false,
1623
- search: false,
1624
- markRead: false,
1625
- move: false,
1626
- delete: true,
1627
- threads: false,
1628
- oauth: false
1629
- };
1630
- }
1631
- getAdapter() {
1632
- return "pop3";
1633
- }
1634
- // ========================================================================
1635
- // Private helper methods
1636
- // ========================================================================
1637
- ensureConnected() {
1638
- if (!this.connected || !this.client) {
1639
- throw new ConnectionError(
1640
- "Not connected to POP3 server. Call connect() first.",
1641
- "pop3"
1642
- );
1643
- }
1644
- }
1645
- async fetchMessage(msgNum) {
1646
- try {
1647
- const response = await this.client?.RETR(Number(msgNum));
1648
- if (!response || !response[0]) {
1649
- throw new Error(`Failed to retrieve message ${msgNum}`);
1650
- }
1651
- const messageData = response[0];
1652
- const parsed = await simpleParser(messageData);
1653
- const uid = this.messageCache.get(msgNum);
1654
- const extractAddresses = (addressObj) => {
1655
- if (!addressObj) return [];
1656
- const addressArray = Array.isArray(addressObj) ? addressObj : [addressObj];
1657
- const addresses = addressArray.flatMap(
1658
- (obj) => Array.isArray(obj.value) ? obj.value : [obj.value]
1659
- );
1660
- return addresses.map((addr) => ({
1661
- address: addr.address || "",
1662
- name: addr.name
1663
- }));
1664
- };
1665
- const fromAddresses = extractAddresses(parsed.from);
1666
- const toAddresses = extractAddresses(parsed.to);
1667
- const ccAddresses = extractAddresses(parsed.cc);
1668
- const bccAddresses = extractAddresses(parsed.bcc);
1669
- const replyToAddresses = extractAddresses(parsed.replyTo);
1670
- return {
1671
- id: uid || msgNum,
1672
- messageId: parsed.messageId || uid || msgNum,
1673
- from: fromAddresses[0] || { address: "", name: void 0 },
1674
- to: toAddresses,
1675
- cc: ccAddresses.length > 0 ? ccAddresses : void 0,
1676
- bcc: bccAddresses.length > 0 ? bccAddresses : void 0,
1677
- replyTo: replyToAddresses[0],
1678
- subject: parsed.subject || "",
1679
- date: parsed.date,
1680
- text: parsed.text,
1681
- html: parsed.html ? parsed.html.toString() : void 0,
1682
- attachments: parsed.attachments?.map((att) => ({
1683
- filename: att.filename,
1684
- contentType: att.contentType,
1685
- size: att.size,
1686
- content: att.content,
1687
- contentId: att.contentId,
1688
- contentDisposition: att.contentDisposition
1689
- })),
1690
- headers: parsed.headers,
1691
- size: messageData.length,
1692
- raw: messageData
1693
- };
1694
- } catch (error) {
1695
- throw this.mapPOP3Error(error);
1696
- }
1697
- }
1698
- async deleteMessageByNum(msgNum) {
1699
- try {
1700
- await this.client?.command(`DELE ${msgNum}`);
1701
- this.messageCache.delete(msgNum);
1702
- this.debug(`Deleted message ${msgNum}`);
1703
- } catch (error) {
1704
- throw this.mapPOP3Error(error);
1705
- }
1706
- }
1707
- parseUidlResponse(response) {
1708
- const lines = response.split("\n");
1709
- const result = [];
1710
- for (const line of lines) {
1711
- const trimmed = line.trim();
1712
- if (!trimmed || trimmed === "." || trimmed.startsWith("+OK")) {
1713
- continue;
1714
- }
1715
- const parts = trimmed.split(/\s+/);
1716
- if (parts.length >= 2) {
1717
- result.push({
1718
- msgNum: parts[0],
1719
- uid: parts[1]
1720
- });
1721
- }
1722
- }
1723
- return result;
1724
- }
1725
- shouldIncludeMessage(message, options) {
1726
- if (options?.since && message.date) {
1727
- if (message.date < options.since) {
1728
- return false;
1729
- }
1730
- }
1731
- if (options?.before && message.date) {
1732
- if (message.date >= options.before) {
1733
- return false;
1734
- }
1735
- }
1736
- return true;
1737
- }
1738
- mapPOP3Error(error) {
1739
- if (error instanceof EmailError) {
1740
- return error;
1741
- }
1742
- if (error instanceof Error) {
1743
- const message = error.message.toLowerCase();
1744
- const errorObj = error;
1745
- if (message.includes("timeout") || errorObj.eventName === "timeout" || errorObj.code === "ETIMEDOUT") {
1746
- return new TimeoutError(error.message, "pop3", error);
1747
- }
1748
- if (message.includes("connect") || message.includes("econnrefused") || message.includes("enotfound") || message.includes("ehostunreach") || errorObj.eventName === "error" || errorObj.eventName === "close" || errorObj.code === "ECONNREFUSED" || errorObj.code === "ENOTFOUND") {
1749
- return new ConnectionError(error.message, "pop3", error);
1750
- }
1751
- if (message.includes("auth") || message.includes("login") || message.includes("password") || message.includes("user") || message.includes("-err")) {
1752
- return new AuthenticationError(error.message, "pop3", error);
1753
- }
1754
- return new EmailError(error.message, "POP3_ERROR", "pop3", error);
1755
- }
1756
- return new EmailError(String(error), "POP3_ERROR", "pop3");
1757
- }
1758
- }
1759
- const pop3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1760
- __proto__: null,
1761
- POP3Adapter
1762
- }, Symbol.toStringTag, { value: "Module" }));
1763
- class SMTPAdapter extends BaseEmailClient {
1764
- transporter;
1765
- options;
1766
- constructor(options) {
1767
- super({
1768
- type: "smtp",
1769
- debug: options.debug
1770
- });
1771
- this.options = options;
1772
- this.transporter = this.createTransporter();
1773
- }
1774
- /**
1775
- * Create Nodemailer transporter with configuration
1776
- */
1777
- createTransporter() {
1778
- const config = {
1779
- host: this.options.host,
1780
- port: this.options.port,
1781
- secure: this.options.secure ?? false,
1782
- auth: this.options.auth,
1783
- tls: this.options.tls,
1784
- connectionTimeout: this.options.connectionTimeout,
1785
- greetingTimeout: this.options.greetingTimeout,
1786
- socketTimeout: this.options.socketTimeout,
1787
- pool: this.options.pool,
1788
- maxConnections: this.options.maxConnections,
1789
- maxMessages: this.options.maxMessages,
1790
- debug: this.options.debug
1791
- };
1792
- return nodemailer.createTransport(config);
1793
- }
1794
- getAdapter() {
1795
- return "smtp";
1796
- }
1797
- /**
1798
- * Send an email message
1799
- */
1800
- async send(message, options) {
1801
- this.validateMessage(message);
1802
- try {
1803
- const mailOptions = {
1804
- from: this.formatEmailAddress(message.from),
1805
- to: message.to.map((addr) => this.formatEmailAddress(addr)),
1806
- cc: message.cc?.map((addr) => this.formatEmailAddress(addr)),
1807
- bcc: message.bcc?.map((addr) => this.formatEmailAddress(addr)),
1808
- subject: message.subject,
1809
- text: message.text,
1810
- html: message.html,
1811
- replyTo: message.replyTo ? this.formatEmailAddress(message.replyTo) : void 0,
1812
- inReplyTo: message.inReplyTo,
1813
- references: message.references,
1814
- attachments: message.attachments?.map((att) => ({
1815
- filename: att.filename,
1816
- content: att.content,
1817
- path: att.path,
1818
- contentType: att.contentType,
1819
- cid: att.contentId,
1820
- contentDisposition: att.contentDisposition
1821
- })),
1822
- headers: message.headers,
1823
- messageId: message.messageId || options?.messageId,
1824
- date: message.date || options?.date,
1825
- encoding: options?.encoding,
1826
- textEncoding: options?.textEncoding,
1827
- envelope: options?.envelope,
1828
- priority: options?.priority,
1829
- dsn: options?.dsn
1830
- };
1831
- this.debug("Sending email", {
1832
- to: mailOptions.to,
1833
- subject: message.subject
1834
- });
1835
- const info = await this.transporter.sendMail(mailOptions);
1836
- const sendInfo = info;
1837
- return {
1838
- messageId: sendInfo.messageId,
1839
- accepted: sendInfo.accepted,
1840
- rejected: sendInfo.rejected,
1841
- response: sendInfo.response
1842
- };
1843
- } catch (error) {
1844
- throw this.mapSMTPError(error);
1845
- }
1846
- }
1847
- /**
1848
- * Format email address for Nodemailer
1849
- */
1850
- formatEmailAddress(addr) {
1851
- if (addr.name) {
1852
- const name = addr.name.replace(/"/g, '\\"');
1853
- return `"${name}" <${addr.address}>`;
1854
- }
1855
- return addr.address;
1856
- }
1857
- /**
1858
- * Map Nodemailer/SMTP errors to standard error types
1859
- */
1860
- mapSMTPError(error) {
1861
- if (error instanceof EmailError) {
1862
- return error;
1863
- }
1864
- if (error instanceof Error) {
1865
- const message = error.message.toLowerCase();
1866
- if (message.includes("authentication") || message.includes("invalid login") || message.includes("535")) {
1867
- return new AuthenticationError(error.message, "smtp", error);
1868
- }
1869
- if (message.includes("timeout") || message.includes("etimedout")) {
1870
- return new TimeoutError(error.message, "smtp", error);
1871
- }
1872
- if (message.includes("econnrefused") || message.includes("enotfound") || message.includes("connection") || message.includes("ehostunreach")) {
1873
- return new ConnectionError(error.message, "smtp", error);
1874
- }
1875
- if ("accepted" in error && "rejected" in error) {
1876
- const errorWithRecipients = error;
1877
- return new SendError(
1878
- error.message,
1879
- errorWithRecipients.accepted || [],
1880
- errorWithRecipients.rejected || [],
1881
- "smtp",
1882
- error
1883
- );
1884
- }
1885
- return new EmailError(error.message, "SMTP_ERROR", "smtp", error);
1886
- }
1887
- return new EmailError(String(error), "UNKNOWN_ERROR", "smtp");
1888
- }
1889
- /**
1890
- * Connect to SMTP server (verify connection)
1891
- */
1892
- async connect() {
1893
- try {
1894
- this.debug("Verifying SMTP connection", { host: this.options.host });
1895
- await this.transporter.verify();
1896
- this.connected = true;
1897
- this.debug("SMTP connection verified");
1898
- } catch (error) {
1899
- this.connected = false;
1900
- throw this.mapSMTPError(error);
1901
- }
1902
- }
1903
- /**
1904
- * Disconnect from SMTP server
1905
- */
1906
- async disconnect() {
1907
- this.transporter.close();
1908
- this.connected = false;
1909
- this.debug("SMTP connection closed");
1910
- }
1911
- /**
1912
- * Get adapter capabilities
1913
- */
1914
- async getCapabilities() {
1915
- return {
1916
- send: true,
1917
- receive: false,
1918
- // SMTP is send-only
1919
- folders: false,
1920
- search: false,
1921
- markRead: false,
1922
- move: false,
1923
- delete: false,
1924
- threads: false,
1925
- oauth: this.isOAuth2()
1926
- };
1927
- }
1928
- /**
1929
- * Check if using OAuth2 authentication
1930
- */
1931
- isOAuth2() {
1932
- return this.options.auth !== void 0 && "type" in this.options.auth && this.options.auth.type === "OAuth2";
1933
- }
1934
- // ========================================================================
1935
- // Unsupported operations (SMTP is send-only)
1936
- // ========================================================================
1937
- async fetch(_options) {
1938
- throw new EmailError(
1939
- "SMTP does not support receiving messages. Use IMAP or POP3 adapter.",
1940
- "UNSUPPORTED_OPERATION",
1941
- "smtp"
1942
- );
1943
- }
1944
- async getMessage(_messageId) {
1945
- throw new EmailError(
1946
- "SMTP does not support receiving messages. Use IMAP or POP3 adapter.",
1947
- "UNSUPPORTED_OPERATION",
1948
- "smtp"
1949
- );
1950
- }
1951
- async listFolders() {
1952
- throw new EmailError(
1953
- "SMTP does not support folder operations.",
1954
- "UNSUPPORTED_OPERATION",
1955
- "smtp"
1956
- );
1957
- }
1958
- async selectFolder(_name) {
1959
- throw new EmailError(
1960
- "SMTP does not support folder operations.",
1961
- "UNSUPPORTED_OPERATION",
1962
- "smtp"
1963
- );
1964
- }
1965
- async createFolder(_name) {
1966
- throw new EmailError(
1967
- "SMTP does not support folder operations.",
1968
- "UNSUPPORTED_OPERATION",
1969
- "smtp"
1970
- );
1971
- }
1972
- async deleteFolder(_name) {
1973
- throw new EmailError(
1974
- "SMTP does not support folder operations.",
1975
- "UNSUPPORTED_OPERATION",
1976
- "smtp"
1977
- );
1978
- }
1979
- async markRead(_messageId) {
1980
- throw new EmailError(
1981
- "SMTP does not support message operations.",
1982
- "UNSUPPORTED_OPERATION",
1983
- "smtp"
1984
- );
1985
- }
1986
- async markUnread(_messageId) {
1987
- throw new EmailError(
1988
- "SMTP does not support message operations.",
1989
- "UNSUPPORTED_OPERATION",
1990
- "smtp"
1991
- );
1992
- }
1993
- async move(_messageId, _folder) {
1994
- throw new EmailError(
1995
- "SMTP does not support message operations.",
1996
- "UNSUPPORTED_OPERATION",
1997
- "smtp"
1998
- );
1999
- }
2000
- async copy(_messageId, _folder) {
2001
- throw new EmailError(
2002
- "SMTP does not support message operations.",
2003
- "UNSUPPORTED_OPERATION",
2004
- "smtp"
2005
- );
2006
- }
2007
- async delete(_messageId) {
2008
- throw new EmailError(
2009
- "SMTP does not support message operations.",
2010
- "UNSUPPORTED_OPERATION",
2011
- "smtp"
2012
- );
2013
- }
2014
- async search(_criteria) {
2015
- throw new EmailError(
2016
- "SMTP does not support search operations.",
2017
- "UNSUPPORTED_OPERATION",
2018
- "smtp"
2019
- );
2020
- }
2021
- }
2022
- const smtp = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
2023
- __proto__: null,
2024
- SMTPAdapter
2025
- }, Symbol.toStringTag, { value: "Module" }));
7
+ //#region \0rolldown/runtime.js
8
+ var __defProp = Object.defineProperty;
9
+ var __exportAll = (all, no_symbols) => {
10
+ let target = {};
11
+ for (var name in all) __defProp(target, name, {
12
+ get: all[name],
13
+ enumerable: true
14
+ });
15
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
16
+ return target;
17
+ };
18
+ //#endregion
19
+ //#region src/adapters/gmail.ts
20
+ var gmail_exports = /* @__PURE__ */ __exportAll({ GmailAdapter: () => GmailAdapter });
21
+ /**
22
+ * Gmail adapter implementation using Gmail API
23
+ *
24
+ * Requires OAuth2 credentials with appropriate scopes:
25
+ * - gmail.readonly: Read emails and settings
26
+ * - gmail.send: Send emails
27
+ * - gmail.modify: Modify emails (mark read, delete, move)
28
+ * - gmail.labels: Manage labels
29
+ */
30
+ var GmailAdapter = class extends BaseEmailClient {
31
+ gmail = null;
32
+ options;
33
+ auth = null;
34
+ constructor(options) {
35
+ super({
36
+ type: "gmail",
37
+ debug: options.debug
38
+ });
39
+ this.options = options;
40
+ }
41
+ async connect() {
42
+ try {
43
+ const auth = new google.auth.OAuth2(this.options.auth.clientId, this.options.auth.clientSecret);
44
+ auth.setCredentials({
45
+ refresh_token: this.options.auth.refreshToken,
46
+ access_token: this.options.auth.accessToken
47
+ });
48
+ this.auth = auth;
49
+ this.gmail = google.gmail({
50
+ version: "v1",
51
+ auth
52
+ });
53
+ await this.gmail.users.getProfile({ userId: this.getUserId() });
54
+ this.connected = true;
55
+ this.debug("Connected to Gmail API");
56
+ } catch (error) {
57
+ throw this.mapGmailError(error);
58
+ }
59
+ }
60
+ async disconnect() {
61
+ this.gmail = null;
62
+ this.auth = null;
63
+ this.connected = false;
64
+ this.debug("Disconnected from Gmail API");
65
+ }
66
+ async send(message, options) {
67
+ this.ensureConnected();
68
+ this.validateMessage(message);
69
+ try {
70
+ const email = this.buildRFC2822Message(message, options);
71
+ const encodedEmail = Buffer.from(email).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
72
+ const response = await this.gmail?.users.messages.send({
73
+ userId: this.getUserId(),
74
+ requestBody: { raw: encodedEmail }
75
+ });
76
+ if (!response) throw new SendError("Failed to send message", [], [], "gmail");
77
+ return {
78
+ messageId: response.data.id || "",
79
+ accepted: message.to.map((addr) => addr.address),
80
+ rejected: [],
81
+ response: `Message sent: ${response.data.id}`
82
+ };
83
+ } catch (error) {
84
+ throw this.mapGmailError(error);
85
+ }
86
+ }
87
+ async fetch(options) {
88
+ this.ensureConnected();
89
+ try {
90
+ const query = this.buildGmailQuery(options);
91
+ let labelIds = options?.labelIds;
92
+ if (options?.folder && !labelIds) labelIds = [options.folder];
93
+ let listResponse;
94
+ try {
95
+ listResponse = await this.gmail?.users.messages.list({
96
+ userId: this.getUserId(),
97
+ q: query,
98
+ labelIds,
99
+ maxResults: options?.maxResults || options?.limit || 100
100
+ });
101
+ } catch (error) {
102
+ if (error instanceof Error && error.message.includes("Invalid label")) return [];
103
+ throw error;
104
+ }
105
+ if (!listResponse) return [];
106
+ const messageIds = listResponse.data.messages || [];
107
+ if (messageIds.length === 0) return [];
108
+ const messages = [];
109
+ for (const msgId of messageIds) {
110
+ if (!msgId.id) continue;
111
+ try {
112
+ const message = await this.getMessage(msgId.id);
113
+ messages.push(message);
114
+ if (options?.limit && messages.length >= options.limit) break;
115
+ } catch (error) {
116
+ this.logger.error(`Failed to fetch message ${msgId.id}:`, { error: error instanceof Error ? error.message : String(error) });
117
+ }
118
+ }
119
+ return messages;
120
+ } catch (error) {
121
+ throw this.mapGmailError(error);
122
+ }
123
+ }
124
+ async getMessage(messageId) {
125
+ this.ensureConnected();
126
+ try {
127
+ const response = await this.gmail?.users.messages.get({
128
+ userId: this.getUserId(),
129
+ id: messageId,
130
+ format: "raw"
131
+ });
132
+ if (!response || !response.data.raw) throw new MessageNotFoundError(messageId, "gmail");
133
+ const rawMessage = Buffer.from(response.data.raw, "base64").toString("utf-8");
134
+ const parsed = await simpleParser(rawMessage);
135
+ const labels = response.data.labelIds || [];
136
+ const extractAddresses = (addressObj) => {
137
+ if (!addressObj) return [];
138
+ return (Array.isArray(addressObj) ? addressObj : [addressObj]).flatMap((obj) => Array.isArray(obj.value) ? obj.value : [obj.value]).map((addr) => ({
139
+ address: addr.address || "",
140
+ name: addr.name
141
+ }));
142
+ };
143
+ const fromAddresses = extractAddresses(parsed.from);
144
+ const toAddresses = extractAddresses(parsed.to);
145
+ const ccAddresses = extractAddresses(parsed.cc);
146
+ const bccAddresses = extractAddresses(parsed.bcc);
147
+ const replyToAddresses = extractAddresses(parsed.replyTo);
148
+ return {
149
+ id: response.data.id || void 0,
150
+ messageId: parsed.messageId || response.data.id || void 0,
151
+ threadId: response.data.threadId || void 0,
152
+ inReplyTo: parsed.inReplyTo,
153
+ references: parsed.references ? Array.isArray(parsed.references) ? parsed.references : [parsed.references] : void 0,
154
+ from: fromAddresses[0] || {
155
+ address: "",
156
+ name: void 0
157
+ },
158
+ to: toAddresses,
159
+ cc: ccAddresses.length > 0 ? ccAddresses : void 0,
160
+ bcc: bccAddresses.length > 0 ? bccAddresses : void 0,
161
+ replyTo: replyToAddresses[0],
162
+ subject: parsed.subject || "",
163
+ date: parsed.date,
164
+ text: parsed.text,
165
+ html: parsed.html ? parsed.html.toString() : void 0,
166
+ attachments: parsed.attachments?.map((att) => ({
167
+ filename: att.filename,
168
+ contentType: att.contentType,
169
+ size: att.size,
170
+ content: att.content,
171
+ contentId: att.contentId,
172
+ contentDisposition: att.contentDisposition
173
+ })),
174
+ labels,
175
+ headers: parsed.headers,
176
+ size: Number(response.data.sizeEstimate),
177
+ raw: rawMessage
178
+ };
179
+ } catch (error) {
180
+ if (error instanceof MessageNotFoundError) throw error;
181
+ throw this.mapGmailError(error);
182
+ }
183
+ }
184
+ async listFolders() {
185
+ this.ensureConnected();
186
+ try {
187
+ const response = await this.gmail?.users.labels.list({ userId: this.getUserId() });
188
+ if (!response) return [];
189
+ return (response.data.labels || []).map((label) => ({
190
+ name: label.name || "",
191
+ path: label.id || "",
192
+ delimiter: "/",
193
+ specialUse: this.mapGmailLabelToSpecialUse(label.type),
194
+ messageCount: label.messagesTotal || void 0,
195
+ unreadCount: label.messagesUnread || void 0
196
+ }));
197
+ } catch (error) {
198
+ throw this.mapGmailError(error);
199
+ }
200
+ }
201
+ async selectFolder(name) {
202
+ this.ensureConnected();
203
+ try {
204
+ const label = (await this.listFolders()).find((l) => l.name === name || l.path === name);
205
+ if (!label) throw new FolderNotFoundError(name, "gmail");
206
+ return {
207
+ name: label.name,
208
+ exists: label.messageCount || 0,
209
+ recent: 0,
210
+ unseen: label.unreadCount || 0,
211
+ uidValidity: 0,
212
+ uidNext: 0,
213
+ flags: [],
214
+ permanentFlags: []
215
+ };
216
+ } catch (error) {
217
+ if (error instanceof FolderNotFoundError) throw error;
218
+ throw this.mapGmailError(error);
219
+ }
220
+ }
221
+ async createFolder(name) {
222
+ this.ensureConnected();
223
+ try {
224
+ if ((await this.listFolders()).some((l) => l.name === name)) throw new FolderExistsError(name, "gmail");
225
+ await this.gmail?.users.labels.create({
226
+ userId: this.getUserId(),
227
+ requestBody: {
228
+ name,
229
+ labelListVisibility: "labelShow",
230
+ messageListVisibility: "show"
231
+ }
232
+ });
233
+ } catch (error) {
234
+ if (error instanceof FolderExistsError) throw error;
235
+ throw this.mapGmailError(error);
236
+ }
237
+ }
238
+ async deleteFolder(name) {
239
+ this.ensureConnected();
240
+ try {
241
+ const label = (await this.listFolders()).find((l) => l.name === name);
242
+ if (!label) throw new FolderNotFoundError(name, "gmail");
243
+ if (label.specialUse) throw new EmailError(`Cannot delete system label: ${name}`, "INVALID_OPERATION", "gmail");
244
+ await this.gmail?.users.labels.delete({
245
+ userId: this.getUserId(),
246
+ id: label.path
247
+ });
248
+ } catch (error) {
249
+ if (error instanceof FolderNotFoundError || error instanceof EmailError) throw error;
250
+ throw this.mapGmailError(error);
251
+ }
252
+ }
253
+ async markRead(messageId) {
254
+ this.ensureConnected();
255
+ const ids = this.normalizeMessageIds(messageId);
256
+ try {
257
+ for (const id of ids) await this.gmail?.users.messages.modify({
258
+ userId: this.getUserId(),
259
+ id,
260
+ requestBody: { removeLabelIds: ["UNREAD"] }
261
+ });
262
+ } catch (error) {
263
+ throw this.mapGmailError(error);
264
+ }
265
+ }
266
+ async markUnread(messageId) {
267
+ this.ensureConnected();
268
+ const ids = this.normalizeMessageIds(messageId);
269
+ try {
270
+ for (const id of ids) await this.gmail?.users.messages.modify({
271
+ userId: this.getUserId(),
272
+ id,
273
+ requestBody: { addLabelIds: ["UNREAD"] }
274
+ });
275
+ } catch (error) {
276
+ throw this.mapGmailError(error);
277
+ }
278
+ }
279
+ async move(messageId, folder) {
280
+ this.ensureConnected();
281
+ const ids = this.normalizeMessageIds(messageId);
282
+ try {
283
+ const label = (await this.listFolders()).find((l) => l.name === folder || l.path === folder);
284
+ if (!label) throw new FolderNotFoundError(folder, "gmail");
285
+ for (const id of ids) await this.gmail?.users.messages.modify({
286
+ userId: this.getUserId(),
287
+ id,
288
+ requestBody: { addLabelIds: [label.path] }
289
+ });
290
+ } catch (error) {
291
+ if (error instanceof FolderNotFoundError) throw error;
292
+ throw this.mapGmailError(error);
293
+ }
294
+ }
295
+ async copy(messageId, folder) {
296
+ await this.move(messageId, folder);
297
+ }
298
+ async delete(messageId) {
299
+ this.ensureConnected();
300
+ const ids = this.normalizeMessageIds(messageId);
301
+ try {
302
+ for (const id of ids) await this.gmail?.users.messages.trash({
303
+ userId: this.getUserId(),
304
+ id
305
+ });
306
+ } catch (error) {
307
+ throw this.mapGmailError(error);
308
+ }
309
+ }
310
+ async search(criteria) {
311
+ this.ensureConnected();
312
+ try {
313
+ if (criteria.q) return await this.fetch({ q: criteria.q });
314
+ const queryParts = [];
315
+ if (criteria.from) queryParts.push(`from:${criteria.from}`);
316
+ if (criteria.to) queryParts.push(`to:${criteria.to}`);
317
+ if (criteria.subject) queryParts.push(`subject:${criteria.subject}`);
318
+ if (criteria.body) queryParts.push(criteria.body);
319
+ if (criteria.since) queryParts.push(`after:${this.formatGmailDate(criteria.since)}`);
320
+ if (criteria.before) queryParts.push(`before:${this.formatGmailDate(criteria.before)}`);
321
+ if (criteria.unread === true) queryParts.push("is:unread");
322
+ if (criteria.unread === false) queryParts.push("is:read");
323
+ if (criteria.flagged) queryParts.push("is:starred");
324
+ if (criteria.larger) queryParts.push(`larger:${criteria.larger}`);
325
+ if (criteria.smaller) queryParts.push(`smaller:${criteria.smaller}`);
326
+ const query = queryParts.join(" ");
327
+ return await this.fetch({ q: query });
328
+ } catch (error) {
329
+ throw this.mapGmailError(error);
330
+ }
331
+ }
332
+ async getCapabilities() {
333
+ return {
334
+ send: true,
335
+ receive: true,
336
+ folders: true,
337
+ search: true,
338
+ markRead: true,
339
+ move: true,
340
+ delete: true,
341
+ threads: true,
342
+ oauth: true
343
+ };
344
+ }
345
+ getAdapter() {
346
+ return "gmail";
347
+ }
348
+ ensureConnected() {
349
+ if (!this.connected || !this.gmail) throw new ConnectionError("Not connected to Gmail. Call connect() first.", "gmail");
350
+ }
351
+ getUserId() {
352
+ return this.options.userId || "me";
353
+ }
354
+ buildRFC2822Message(message, options) {
355
+ const lines = [];
356
+ const hasAttachments = message.attachments && message.attachments.length > 0;
357
+ lines.push(`From: ${this.formatEmailAddress(message.from)}`);
358
+ lines.push(`To: ${message.to.map((a) => this.formatEmailAddress(a)).join(", ")}`);
359
+ if (message.cc && message.cc.length > 0) lines.push(`Cc: ${message.cc.map((a) => this.formatEmailAddress(a)).join(", ")}`);
360
+ if (message.replyTo) lines.push(`Reply-To: ${this.formatEmailAddress(message.replyTo)}`);
361
+ lines.push(`Subject: ${message.subject}`);
362
+ lines.push(`Date: ${options?.date?.toUTCString() || (/* @__PURE__ */ new Date()).toUTCString()}`);
363
+ if (options?.messageId) lines.push(`Message-ID: <${options.messageId}>`);
364
+ lines.push("MIME-Version: 1.0");
365
+ if (hasAttachments) {
366
+ lines.push("Content-Type: multipart/mixed; boundary=\"mixed-boundary\"");
367
+ lines.push("");
368
+ lines.push("--mixed-boundary");
369
+ if (message.html) {
370
+ lines.push("Content-Type: multipart/alternative; boundary=\"alt-boundary\"");
371
+ lines.push("");
372
+ lines.push("--alt-boundary");
373
+ lines.push("Content-Type: text/plain; charset=\"UTF-8\"");
374
+ lines.push("Content-Transfer-Encoding: 7bit");
375
+ lines.push("");
376
+ lines.push(message.text || "");
377
+ lines.push("");
378
+ lines.push("--alt-boundary");
379
+ lines.push("Content-Type: text/html; charset=\"UTF-8\"");
380
+ lines.push("Content-Transfer-Encoding: 7bit");
381
+ lines.push("");
382
+ lines.push(message.html);
383
+ lines.push("");
384
+ lines.push("--alt-boundary--");
385
+ } else {
386
+ lines.push("Content-Type: text/plain; charset=\"UTF-8\"");
387
+ lines.push("Content-Transfer-Encoding: 7bit");
388
+ lines.push("");
389
+ lines.push(message.text || "");
390
+ }
391
+ if (message.attachments) for (const attachment of message.attachments) {
392
+ lines.push("");
393
+ lines.push("--mixed-boundary");
394
+ lines.push(`Content-Type: ${attachment.contentType}; name="${attachment.filename}"`);
395
+ lines.push("Content-Transfer-Encoding: base64");
396
+ lines.push(`Content-Disposition: ${attachment.contentDisposition || "attachment"}; filename="${attachment.filename}"`);
397
+ if (attachment.contentId) lines.push(`Content-ID: <${attachment.contentId}>`);
398
+ lines.push("");
399
+ const base64Lines = (attachment.content || Buffer.from("")).toString("base64").match(/.{1,76}/g) || [];
400
+ lines.push(...base64Lines);
401
+ }
402
+ lines.push("");
403
+ lines.push("--mixed-boundary--");
404
+ } else if (message.html) {
405
+ lines.push("Content-Type: multipart/alternative; boundary=\"alt-boundary\"");
406
+ lines.push("");
407
+ lines.push("--alt-boundary");
408
+ lines.push("Content-Type: text/plain; charset=\"UTF-8\"");
409
+ lines.push("Content-Transfer-Encoding: 7bit");
410
+ lines.push("");
411
+ lines.push(message.text || "");
412
+ lines.push("");
413
+ lines.push("--alt-boundary");
414
+ lines.push("Content-Type: text/html; charset=\"UTF-8\"");
415
+ lines.push("Content-Transfer-Encoding: 7bit");
416
+ lines.push("");
417
+ lines.push(message.html);
418
+ lines.push("");
419
+ lines.push("--alt-boundary--");
420
+ } else {
421
+ lines.push("Content-Type: text/plain; charset=\"UTF-8\"");
422
+ lines.push("Content-Transfer-Encoding: 7bit");
423
+ lines.push("");
424
+ lines.push(message.text || "");
425
+ }
426
+ return lines.join("\r\n");
427
+ }
428
+ formatEmailAddress(addr) {
429
+ if (addr.name) return `${addr.name} <${addr.address}>`;
430
+ return addr.address;
431
+ }
432
+ buildGmailQuery(options) {
433
+ const queryParts = [];
434
+ if (options?.q) return options.q;
435
+ if (options?.since) queryParts.push(`after:${this.formatGmailDate(options.since)}`);
436
+ if (options?.before) queryParts.push(`before:${this.formatGmailDate(options.before)}`);
437
+ if (options?.unreadOnly) queryParts.push("is:unread");
438
+ return queryParts.join(" ");
439
+ }
440
+ formatGmailDate(date) {
441
+ return `${date.getFullYear()}/${String(date.getMonth() + 1).padStart(2, "0")}/${String(date.getDate()).padStart(2, "0")}`;
442
+ }
443
+ mapGmailLabelToSpecialUse(type) {
444
+ switch (type) {
445
+ case "system": return "\\Inbox";
446
+ default: return;
447
+ }
448
+ }
449
+ mapGmailError(error) {
450
+ if (error instanceof EmailError) return error;
451
+ if (error instanceof Error) {
452
+ const message = error.message.toLowerCase();
453
+ const errorObj = error;
454
+ if (message.includes("timeout") || errorObj.code === "ETIMEDOUT" || errorObj.code === "ESOCKETTIMEDOUT") return new TimeoutError(error.message, "gmail", error);
455
+ if (message.includes("network") || message.includes("connect") || errorObj.code === "ECONNREFUSED" || errorObj.code === "ENOTFOUND" || errorObj.code === "ECONNRESET") return new ConnectionError(error.message, "gmail", error);
456
+ if (message.includes("auth") || message.includes("invalid_grant") || message.includes("unauthorized") || errorObj.code === 401) return new AuthenticationError(error.message, "gmail", error);
457
+ if (message.includes("recipient") || message.includes("invalid email")) return new SendError(error.message, [], [], "gmail", error);
458
+ return new EmailError(error.message, "GMAIL_ERROR", "gmail", error);
459
+ }
460
+ return new EmailError(String(error), "GMAIL_ERROR", "gmail");
461
+ }
462
+ };
463
+ //#endregion
464
+ //#region src/adapters/imap.ts
465
+ var imap_exports = /* @__PURE__ */ __exportAll({ IMAPAdapter: () => IMAPAdapter });
466
+ /**
467
+ * IMAP adapter for receiving email via IMAP protocol
468
+ *
469
+ * Uses ImapFlow for robust IMAP operations including:
470
+ * - Fetch messages with various filters
471
+ * - Folder management (list, create, delete, rename)
472
+ * - Message operations (mark read, move, copy, delete)
473
+ * - Search functionality
474
+ * - OAuth2 authentication
475
+ * - IDLE push notifications (optional)
476
+ */
477
+ var IMAPAdapter = class extends BaseEmailClient {
478
+ client = null;
479
+ options;
480
+ currentFolder = null;
481
+ constructor(options) {
482
+ super({
483
+ type: "imap",
484
+ debug: options.debug
485
+ });
486
+ this.options = options;
487
+ }
488
+ /**
489
+ * Create ImapFlow client
490
+ */
491
+ createClient() {
492
+ return new ImapFlow({
493
+ host: this.options.host,
494
+ port: this.options.port,
495
+ secure: this.options.secure ?? true,
496
+ auth: this.options.auth,
497
+ tls: this.options.tls,
498
+ logger: this.options.debug ? {
499
+ debug: (msg) => this.debug(msg),
500
+ info: (msg) => this.debug(msg),
501
+ warn: (msg) => this.debug(`WARN: ${msg}`),
502
+ error: (msg) => this.debug(`ERROR: ${msg}`)
503
+ } : false
504
+ });
505
+ }
506
+ getAdapter() {
507
+ return "imap";
508
+ }
509
+ /**
510
+ * Helper to convert search results to array
511
+ */
512
+ searchResultToArray(result) {
513
+ if (!result || !Array.isArray(result)) return [];
514
+ return result;
515
+ }
516
+ /**
517
+ * Connect to IMAP server
518
+ */
519
+ async connect() {
520
+ try {
521
+ this.debug("Connecting to IMAP server", { host: this.options.host });
522
+ this.client = this.createClient();
523
+ await this.client.connect();
524
+ this.connected = true;
525
+ this.debug("IMAP connection established");
526
+ } catch (error) {
527
+ this.connected = false;
528
+ throw this.mapIMAPError(error);
529
+ }
530
+ }
531
+ /**
532
+ * Disconnect from IMAP server
533
+ */
534
+ async disconnect() {
535
+ if (this.client) {
536
+ await this.client.logout();
537
+ this.client = null;
538
+ this.connected = false;
539
+ this.currentFolder = null;
540
+ this.debug("IMAP connection closed");
541
+ }
542
+ }
543
+ /**
544
+ * Fetch messages with filters
545
+ */
546
+ async fetch(options) {
547
+ this.ensureConnected();
548
+ const folder = options?.folder || "INBOX";
549
+ await this.selectFolder(folder);
550
+ try {
551
+ const searchQuery = this.buildSearchQuery(options);
552
+ this.debug("Fetching messages", {
553
+ folder,
554
+ query: searchQuery
555
+ });
556
+ const searchResult = await this.client.search(searchQuery, { uid: true });
557
+ const uids = this.searchResultToArray(searchResult);
558
+ let targetUids = uids;
559
+ if (options?.offset || options?.limit) {
560
+ const start = options.offset || 0;
561
+ const end = options.limit ? start + options.limit : void 0;
562
+ targetUids = uids.slice(start, end);
563
+ }
564
+ if (targetUids.length === 0) return [];
565
+ const messages = [];
566
+ for await (const msg of this.client.fetch(targetUids, {
567
+ source: true,
568
+ flags: true,
569
+ uid: true,
570
+ envelope: true,
571
+ bodyStructure: true
572
+ })) try {
573
+ if (!msg.source) {
574
+ this.debug("Message has no source", { uid: msg.uid });
575
+ continue;
576
+ }
577
+ const parsed = await simpleParser(msg.source);
578
+ const email = this.parseMessage(parsed, msg);
579
+ messages.push(email);
580
+ if (options?.markSeen) await this.client.messageFlagsAdd(msg.uid, ["\\Seen"], { uid: true });
581
+ } catch (parseError) {
582
+ this.debug("Failed to parse message", {
583
+ uid: msg.uid,
584
+ parseError
585
+ });
586
+ }
587
+ return messages;
588
+ } catch (error) {
589
+ throw this.mapIMAPError(error);
590
+ }
591
+ }
592
+ /**
593
+ * Get a specific message by ID
594
+ */
595
+ async getMessage(messageId) {
596
+ this.ensureConnected();
597
+ try {
598
+ const searchResult = await this.client.search({ header: ["message-id", messageId] }, { uid: true });
599
+ const uids = this.searchResultToArray(searchResult);
600
+ if (uids.length === 0) throw new MessageNotFoundError(messageId, "imap");
601
+ const messages = [];
602
+ for await (const msg of this.client.fetch(uids[0], {
603
+ source: true,
604
+ flags: true,
605
+ uid: true
606
+ })) {
607
+ if (!msg.source) continue;
608
+ const parsed = await simpleParser(msg.source);
609
+ messages.push(this.parseMessage(parsed, msg));
610
+ }
611
+ if (messages.length === 0) throw new MessageNotFoundError(messageId, "imap");
612
+ return messages[0];
613
+ } catch (error) {
614
+ if (error instanceof EmailError) throw error;
615
+ throw this.mapIMAPError(error);
616
+ }
617
+ }
618
+ /**
619
+ * List all folders
620
+ */
621
+ async listFolders() {
622
+ this.ensureConnected();
623
+ try {
624
+ const list = await this.client?.list();
625
+ if (!list) return [];
626
+ return list.map((folder) => ({
627
+ name: folder.name,
628
+ path: folder.path,
629
+ delimiter: folder.delimiter,
630
+ specialUse: folder.specialUse,
631
+ subscribed: folder.subscribed
632
+ }));
633
+ } catch (error) {
634
+ throw this.mapIMAPError(error);
635
+ }
636
+ }
637
+ /**
638
+ * Select a folder
639
+ */
640
+ async selectFolder(name) {
641
+ this.ensureConnected();
642
+ try {
643
+ const mailbox = await this.client?.mailboxOpen(name);
644
+ this.currentFolder = name;
645
+ if (!mailbox) throw new FolderNotFoundError(name, "imap");
646
+ return {
647
+ name,
648
+ exists: Number(mailbox.exists),
649
+ recent: 0,
650
+ unseen: 0,
651
+ uidValidity: Number(mailbox.uidValidity),
652
+ uidNext: Number(mailbox.uidNext),
653
+ flags: Array.from(mailbox.flags || []),
654
+ permanentFlags: Array.from(mailbox.permanentFlags || [])
655
+ };
656
+ } catch (error) {
657
+ if (error instanceof Error && error.message.toLowerCase().includes("does not exist")) throw new FolderNotFoundError(name, "imap");
658
+ throw this.mapIMAPError(error);
659
+ }
660
+ }
661
+ /**
662
+ * Create a new folder
663
+ */
664
+ async createFolder(name) {
665
+ this.ensureConnected();
666
+ try {
667
+ await this.client?.mailboxCreate(name);
668
+ this.debug("Folder created", { name });
669
+ } catch (error) {
670
+ if (error instanceof Error && error.message.toLowerCase().includes("already exists")) throw new FolderExistsError(name, "imap");
671
+ throw this.mapIMAPError(error);
672
+ }
673
+ }
674
+ /**
675
+ * Delete a folder
676
+ */
677
+ async deleteFolder(name) {
678
+ this.ensureConnected();
679
+ try {
680
+ await this.client?.mailboxDelete(name);
681
+ this.debug("Folder deleted", { name });
682
+ } catch (error) {
683
+ if (error instanceof Error && error.message.toLowerCase().includes("does not exist")) throw new FolderNotFoundError(name, "imap");
684
+ throw this.mapIMAPError(error);
685
+ }
686
+ }
687
+ /**
688
+ * Mark message(s) as read
689
+ */
690
+ async markRead(messageId) {
691
+ this.ensureConnected();
692
+ const ids = Array.isArray(messageId) ? messageId : [messageId];
693
+ try {
694
+ for (const id of ids) {
695
+ const searchResult = await this.client?.search({ header: ["message-id", id] }, { uid: true });
696
+ const uids = this.searchResultToArray(searchResult);
697
+ if (uids.length > 0) await this.client?.messageFlagsAdd(uids[0], ["\\Seen"], { uid: true });
698
+ }
699
+ } catch (error) {
700
+ throw this.mapIMAPError(error);
701
+ }
702
+ }
703
+ /**
704
+ * Mark message(s) as unread
705
+ */
706
+ async markUnread(messageId) {
707
+ this.ensureConnected();
708
+ const ids = Array.isArray(messageId) ? messageId : [messageId];
709
+ try {
710
+ for (const id of ids) {
711
+ const searchResult = await this.client?.search({ header: ["message-id", id] }, { uid: true });
712
+ const uids = this.searchResultToArray(searchResult);
713
+ if (uids.length > 0) await this.client?.messageFlagsRemove(uids[0], ["\\Seen"], { uid: true });
714
+ }
715
+ } catch (error) {
716
+ throw this.mapIMAPError(error);
717
+ }
718
+ }
719
+ /**
720
+ * Move message(s) to another folder
721
+ */
722
+ async move(messageId, folder) {
723
+ this.ensureConnected();
724
+ const ids = Array.isArray(messageId) ? messageId : [messageId];
725
+ try {
726
+ for (const id of ids) {
727
+ const searchResult = await this.client?.search({ header: ["message-id", id] }, { uid: true });
728
+ const uids = this.searchResultToArray(searchResult);
729
+ if (uids.length > 0) await this.client?.messageMove(uids[0], folder, { uid: true });
730
+ }
731
+ } catch (error) {
732
+ throw this.mapIMAPError(error);
733
+ }
734
+ }
735
+ /**
736
+ * Copy message(s) to another folder
737
+ */
738
+ async copy(messageId, folder) {
739
+ this.ensureConnected();
740
+ const ids = Array.isArray(messageId) ? messageId : [messageId];
741
+ try {
742
+ for (const id of ids) {
743
+ const searchResult = await this.client?.search({ header: ["message-id", id] }, { uid: true });
744
+ const uids = this.searchResultToArray(searchResult);
745
+ if (uids.length > 0) await this.client?.messageCopy(uids[0], folder, { uid: true });
746
+ }
747
+ } catch (error) {
748
+ throw this.mapIMAPError(error);
749
+ }
750
+ }
751
+ /**
752
+ * Delete message(s)
753
+ */
754
+ async delete(messageId) {
755
+ this.ensureConnected();
756
+ const ids = Array.isArray(messageId) ? messageId : [messageId];
757
+ try {
758
+ for (const id of ids) {
759
+ const searchResult = await this.client?.search({ header: ["message-id", id] }, { uid: true });
760
+ const uids = this.searchResultToArray(searchResult);
761
+ if (uids.length > 0) await this.client?.messageFlagsAdd(uids[0], ["\\Deleted"], { uid: true });
762
+ }
763
+ } catch (error) {
764
+ throw this.mapIMAPError(error);
765
+ }
766
+ }
767
+ /**
768
+ * Search messages
769
+ */
770
+ async search(criteria) {
771
+ this.ensureConnected();
772
+ try {
773
+ const query = this.buildSearchCriteria(criteria);
774
+ const searchResult = await this.client.search(query, { uid: true });
775
+ const uids = this.searchResultToArray(searchResult);
776
+ if (uids.length === 0) return [];
777
+ const messages = [];
778
+ for await (const msg of this.client.fetch(uids, {
779
+ source: true,
780
+ flags: true,
781
+ uid: true
782
+ })) {
783
+ if (!msg.source) continue;
784
+ const parsed = await simpleParser(msg.source);
785
+ messages.push(this.parseMessage(parsed, msg));
786
+ }
787
+ return messages;
788
+ } catch (error) {
789
+ throw this.mapIMAPError(error);
790
+ }
791
+ }
792
+ /**
793
+ * Get adapter capabilities
794
+ */
795
+ async getCapabilities() {
796
+ return {
797
+ send: false,
798
+ receive: true,
799
+ folders: true,
800
+ search: true,
801
+ markRead: true,
802
+ move: true,
803
+ delete: true,
804
+ threads: false,
805
+ oauth: this.isOAuth2()
806
+ };
807
+ }
808
+ /**
809
+ * Check if using OAuth2 authentication
810
+ */
811
+ isOAuth2() {
812
+ return this.options.auth !== void 0 && "type" in this.options.auth && this.options.auth.type === "OAuth2";
813
+ }
814
+ async send(_message, _options) {
815
+ throw new EmailError("IMAP does not support sending messages. Use SMTP adapter.", "UNSUPPORTED_OPERATION", "imap");
816
+ }
817
+ /**
818
+ * Ensure client is connected
819
+ */
820
+ ensureConnected() {
821
+ if (!this.client || !this.connected) throw new ConnectionError("Not connected to IMAP server. Call connect() first.", "imap");
822
+ }
823
+ /**
824
+ * Build IMAP search query from FetchOptions
825
+ */
826
+ buildSearchQuery(options) {
827
+ const query = {};
828
+ if (options?.unreadOnly) query.unseen = true;
829
+ if (options?.since) query.since = options.since;
830
+ if (options?.before) query.before = options.before;
831
+ if (Object.keys(query).length === 0) query.all = true;
832
+ return query;
833
+ }
834
+ /**
835
+ * Build IMAP search criteria from SearchCriteria
836
+ */
837
+ buildSearchCriteria(criteria) {
838
+ const query = {};
839
+ if (criteria.from) query.from = criteria.from;
840
+ if (criteria.to) query.to = criteria.to;
841
+ if (criteria.subject) query.subject = criteria.subject;
842
+ if (criteria.body) query.body = criteria.body;
843
+ if (criteria.since) query.since = criteria.since;
844
+ if (criteria.before) query.before = criteria.before;
845
+ if (criteria.unread) query.unseen = true;
846
+ if (criteria.flagged) query.flagged = true;
847
+ if (criteria.answered) query.answered = true;
848
+ if (criteria.draft) query.draft = true;
849
+ if (criteria.deleted) query.deleted = true;
850
+ if (criteria.larger) query.larger = criteria.larger;
851
+ if (criteria.smaller) query.smaller = criteria.smaller;
852
+ if (criteria.header) query.header = Object.entries(criteria.header).map(([key, value]) => [key, value]);
853
+ if (Object.keys(query).length === 0) query.all = true;
854
+ return query;
855
+ }
856
+ /**
857
+ * Parse mailparser output to EmailMessage
858
+ */
859
+ parseMessage(parsed, msg) {
860
+ const extractAddresses = (addressObj) => {
861
+ if (!addressObj) return [];
862
+ return (Array.isArray(addressObj) ? addressObj : [addressObj]).flatMap((obj) => Array.isArray(obj.value) ? obj.value : [obj.value]).map((addr) => ({
863
+ address: addr.address || "",
864
+ name: addr.name
865
+ }));
866
+ };
867
+ const fromAddresses = extractAddresses(parsed.from);
868
+ const toAddresses = extractAddresses(parsed.to);
869
+ const ccAddresses = extractAddresses(parsed.cc);
870
+ const bccAddresses = extractAddresses(parsed.bcc);
871
+ const replyToAddresses = extractAddresses(parsed.replyTo);
872
+ return {
873
+ id: String(msg.uid),
874
+ messageId: parsed.messageId || void 0,
875
+ from: fromAddresses[0] || {
876
+ address: "",
877
+ name: void 0
878
+ },
879
+ to: toAddresses,
880
+ cc: ccAddresses.length > 0 ? ccAddresses : void 0,
881
+ bcc: bccAddresses.length > 0 ? bccAddresses : void 0,
882
+ replyTo: replyToAddresses[0],
883
+ subject: parsed.subject || "",
884
+ date: parsed.date,
885
+ text: parsed.text,
886
+ html: parsed.html ? String(parsed.html) : void 0,
887
+ attachments: parsed.attachments?.map((att) => ({
888
+ filename: att.filename,
889
+ contentType: att.contentType,
890
+ size: att.size,
891
+ content: att.content,
892
+ contentId: att.contentId,
893
+ contentDisposition: att.contentDisposition
894
+ })),
895
+ flags: msg.flags ? Array.from(msg.flags) : void 0,
896
+ inReplyTo: parsed.inReplyTo,
897
+ references: Array.isArray(parsed.references) ? parsed.references : parsed.references ? [parsed.references] : void 0,
898
+ headers: parsed.headers ? Object.fromEntries(Array.from(parsed.headers.entries()).map(([key, value]) => [key, Array.isArray(value) ? value : String(value)])) : void 0
899
+ };
900
+ }
901
+ /**
902
+ * Map ImapFlow errors to standard error types
903
+ */
904
+ mapIMAPError(error) {
905
+ if (error instanceof EmailError) return error;
906
+ if (error instanceof Error) {
907
+ const message = error.message.toLowerCase();
908
+ if (message.includes("authentication") || message.includes("auth") || message.includes("login") || message.includes("invalid credentials")) return new AuthenticationError(error.message, "imap", error);
909
+ if (message.includes("timeout") || message.includes("etimedout")) return new TimeoutError(error.message, "imap", error);
910
+ if (message.includes("econnrefused") || message.includes("enotfound") || message.includes("connection") || message.includes("ehostunreach") || message.includes("network")) return new ConnectionError(error.message, "imap", error);
911
+ return new EmailError(error.message, "IMAP_ERROR", "imap", error);
912
+ }
913
+ return new EmailError(String(error), "UNKNOWN_ERROR", "imap");
914
+ }
915
+ };
916
+ //#endregion
917
+ //#region src/adapters/pop3.ts
918
+ /**
919
+ * POP3 adapter for receiving email
920
+ */
921
+ var pop3_exports = /* @__PURE__ */ __exportAll({ POP3Adapter: () => POP3Adapter });
922
+ /**
923
+ * POP3 adapter implementation
924
+ *
925
+ * POP3 is a simple protocol for retrieving email. It does not support:
926
+ * - Folders (POP3 has no folder concept)
927
+ * - Marking messages as read/unread
928
+ * - Moving or copying messages
929
+ * - Server-side search
930
+ *
931
+ * By default, POP3 deletes messages from the server after retrieval.
932
+ * Use leaveOnServer: true to keep messages on the server.
933
+ */
934
+ var POP3Adapter = class extends BaseEmailClient {
935
+ client = null;
936
+ options;
937
+ messageCache = /* @__PURE__ */ new Map();
938
+ constructor(options) {
939
+ super({
940
+ type: "pop3",
941
+ debug: options.debug
942
+ });
943
+ this.options = options;
944
+ }
945
+ async connect() {
946
+ try {
947
+ this.client = new Pop3Command({
948
+ user: this.options.auth.user,
949
+ password: this.options.auth.pass,
950
+ host: this.options.host,
951
+ port: this.options.port,
952
+ tls: this.options.secure ?? this.options.port === 995,
953
+ timeout: this.options.connectionTimeout,
954
+ tlsOptions: this.options.tls
955
+ });
956
+ await this.client.connect();
957
+ this.connected = true;
958
+ this.debug("Connected to POP3 server");
959
+ } catch (error) {
960
+ throw this.mapPOP3Error(error);
961
+ }
962
+ }
963
+ async disconnect() {
964
+ if (!this.client) return;
965
+ try {
966
+ await this.client.QUIT();
967
+ this.client = null;
968
+ this.connected = false;
969
+ this.messageCache.clear();
970
+ this.debug("Disconnected from POP3 server");
971
+ } catch (error) {
972
+ this.client = null;
973
+ this.connected = false;
974
+ this.messageCache.clear();
975
+ throw this.mapPOP3Error(error);
976
+ }
977
+ }
978
+ async fetch(options) {
979
+ this.ensureConnected();
980
+ try {
981
+ const uidlResponse = await this.client?.UIDL();
982
+ if (!uidlResponse || !uidlResponse[0]) return [];
983
+ const uidList = this.parseUidlResponse(Array.isArray(uidlResponse[0]) ? uidlResponse[0].join("\n") : uidlResponse[0]);
984
+ if (uidList.length === 0) return [];
985
+ this.messageCache.clear();
986
+ for (const { msgNum, uid } of uidList) this.messageCache.set(msgNum, uid);
987
+ let messageNums = uidList.map((item) => item.msgNum);
988
+ if (options?.offset) messageNums = messageNums.slice(options.offset);
989
+ if (options?.limit) messageNums = messageNums.slice(0, options.limit);
990
+ const messages = [];
991
+ for (const msgNum of messageNums) try {
992
+ const message = await this.fetchMessage(msgNum);
993
+ if (this.shouldIncludeMessage(message, options)) {
994
+ messages.push(message);
995
+ if (!this.options.leaveOnServer) await this.deleteMessageByNum(msgNum);
996
+ }
997
+ } catch (error) {
998
+ this.logger.error(`Failed to fetch message ${msgNum}:`, { error: error instanceof Error ? error.message : String(error) });
999
+ }
1000
+ return messages;
1001
+ } catch (error) {
1002
+ throw this.mapPOP3Error(error);
1003
+ }
1004
+ }
1005
+ async getMessage(messageId) {
1006
+ this.ensureConnected();
1007
+ try {
1008
+ let msgNum = null;
1009
+ for (const [num, uid] of this.messageCache.entries()) if (uid === messageId) {
1010
+ msgNum = num;
1011
+ break;
1012
+ }
1013
+ if (!msgNum) {
1014
+ const uidlResponse = await this.client?.UIDL();
1015
+ if (!uidlResponse || !uidlResponse[0]) throw new MessageNotFoundError(messageId, "pop3");
1016
+ const uidList = this.parseUidlResponse(Array.isArray(uidlResponse[0]) ? uidlResponse[0].join("\n") : uidlResponse[0]);
1017
+ this.messageCache.clear();
1018
+ for (const { msgNum: num, uid } of uidList) {
1019
+ this.messageCache.set(num, uid);
1020
+ if (uid === messageId) msgNum = num;
1021
+ }
1022
+ }
1023
+ if (!msgNum) throw new MessageNotFoundError(messageId, "pop3");
1024
+ return await this.fetchMessage(msgNum);
1025
+ } catch (error) {
1026
+ if (error instanceof MessageNotFoundError) throw error;
1027
+ throw this.mapPOP3Error(error);
1028
+ }
1029
+ }
1030
+ async delete(messageId) {
1031
+ this.ensureConnected();
1032
+ const ids = this.normalizeMessageIds(messageId);
1033
+ for (const id of ids) try {
1034
+ let msgNum = null;
1035
+ for (const [num, uid] of this.messageCache.entries()) if (uid === id) {
1036
+ msgNum = num;
1037
+ break;
1038
+ }
1039
+ if (!msgNum) {
1040
+ const uidlResponse = await this.client?.UIDL();
1041
+ if (!uidlResponse || !uidlResponse[0]) throw new MessageNotFoundError(id, "pop3");
1042
+ const uidList = this.parseUidlResponse(Array.isArray(uidlResponse[0]) ? uidlResponse[0].join("\n") : uidlResponse[0]);
1043
+ for (const { msgNum: num, uid } of uidList) if (uid === id) {
1044
+ msgNum = num;
1045
+ break;
1046
+ }
1047
+ }
1048
+ if (!msgNum) throw new MessageNotFoundError(id, "pop3");
1049
+ await this.deleteMessageByNum(msgNum);
1050
+ } catch (error) {
1051
+ throw this.mapPOP3Error(error);
1052
+ }
1053
+ }
1054
+ async send(_message, _options) {
1055
+ throw new EmailError("POP3 does not support sending email. Use SMTP instead.", "UNSUPPORTED_OPERATION", "pop3");
1056
+ }
1057
+ async listFolders() {
1058
+ throw new EmailError("POP3 does not support folders", "UNSUPPORTED_OPERATION", "pop3");
1059
+ }
1060
+ async selectFolder(_name) {
1061
+ throw new EmailError("POP3 does not support folders", "UNSUPPORTED_OPERATION", "pop3");
1062
+ }
1063
+ async createFolder(_name) {
1064
+ throw new EmailError("POP3 does not support folders", "UNSUPPORTED_OPERATION", "pop3");
1065
+ }
1066
+ async deleteFolder(_name) {
1067
+ throw new EmailError("POP3 does not support folders", "UNSUPPORTED_OPERATION", "pop3");
1068
+ }
1069
+ async markRead(_messageId) {
1070
+ throw new EmailError("POP3 does not support marking messages as read", "UNSUPPORTED_OPERATION", "pop3");
1071
+ }
1072
+ async markUnread(_messageId) {
1073
+ throw new EmailError("POP3 does not support marking messages as unread", "UNSUPPORTED_OPERATION", "pop3");
1074
+ }
1075
+ async move(_messageId, _folder) {
1076
+ throw new EmailError("POP3 does not support moving messages", "UNSUPPORTED_OPERATION", "pop3");
1077
+ }
1078
+ async copy(_messageId, _folder) {
1079
+ throw new EmailError("POP3 does not support copying messages", "UNSUPPORTED_OPERATION", "pop3");
1080
+ }
1081
+ async search(_criteria) {
1082
+ throw new EmailError("POP3 does not support server-side search. Use fetch() with filters instead.", "UNSUPPORTED_OPERATION", "pop3");
1083
+ }
1084
+ async getCapabilities() {
1085
+ return {
1086
+ send: false,
1087
+ receive: true,
1088
+ folders: false,
1089
+ search: false,
1090
+ markRead: false,
1091
+ move: false,
1092
+ delete: true,
1093
+ threads: false,
1094
+ oauth: false
1095
+ };
1096
+ }
1097
+ getAdapter() {
1098
+ return "pop3";
1099
+ }
1100
+ ensureConnected() {
1101
+ if (!this.connected || !this.client) throw new ConnectionError("Not connected to POP3 server. Call connect() first.", "pop3");
1102
+ }
1103
+ async fetchMessage(msgNum) {
1104
+ try {
1105
+ const response = await this.client?.RETR(Number(msgNum));
1106
+ if (!response || !response[0]) throw new Error(`Failed to retrieve message ${msgNum}`);
1107
+ const messageData = response[0];
1108
+ const parsed = await simpleParser(messageData);
1109
+ const uid = this.messageCache.get(msgNum);
1110
+ const extractAddresses = (addressObj) => {
1111
+ if (!addressObj) return [];
1112
+ return (Array.isArray(addressObj) ? addressObj : [addressObj]).flatMap((obj) => Array.isArray(obj.value) ? obj.value : [obj.value]).map((addr) => ({
1113
+ address: addr.address || "",
1114
+ name: addr.name
1115
+ }));
1116
+ };
1117
+ const fromAddresses = extractAddresses(parsed.from);
1118
+ const toAddresses = extractAddresses(parsed.to);
1119
+ const ccAddresses = extractAddresses(parsed.cc);
1120
+ const bccAddresses = extractAddresses(parsed.bcc);
1121
+ const replyToAddresses = extractAddresses(parsed.replyTo);
1122
+ return {
1123
+ id: uid || msgNum,
1124
+ messageId: parsed.messageId || uid || msgNum,
1125
+ from: fromAddresses[0] || {
1126
+ address: "",
1127
+ name: void 0
1128
+ },
1129
+ to: toAddresses,
1130
+ cc: ccAddresses.length > 0 ? ccAddresses : void 0,
1131
+ bcc: bccAddresses.length > 0 ? bccAddresses : void 0,
1132
+ replyTo: replyToAddresses[0],
1133
+ subject: parsed.subject || "",
1134
+ date: parsed.date,
1135
+ text: parsed.text,
1136
+ html: parsed.html ? parsed.html.toString() : void 0,
1137
+ attachments: parsed.attachments?.map((att) => ({
1138
+ filename: att.filename,
1139
+ contentType: att.contentType,
1140
+ size: att.size,
1141
+ content: att.content,
1142
+ contentId: att.contentId,
1143
+ contentDisposition: att.contentDisposition
1144
+ })),
1145
+ headers: parsed.headers,
1146
+ size: messageData.length,
1147
+ raw: messageData
1148
+ };
1149
+ } catch (error) {
1150
+ throw this.mapPOP3Error(error);
1151
+ }
1152
+ }
1153
+ async deleteMessageByNum(msgNum) {
1154
+ try {
1155
+ await this.client?.command(`DELE ${msgNum}`);
1156
+ this.messageCache.delete(msgNum);
1157
+ this.debug(`Deleted message ${msgNum}`);
1158
+ } catch (error) {
1159
+ throw this.mapPOP3Error(error);
1160
+ }
1161
+ }
1162
+ parseUidlResponse(response) {
1163
+ const lines = response.split("\n");
1164
+ const result = [];
1165
+ for (const line of lines) {
1166
+ const trimmed = line.trim();
1167
+ if (!trimmed || trimmed === "." || trimmed.startsWith("+OK")) continue;
1168
+ const parts = trimmed.split(/\s+/);
1169
+ if (parts.length >= 2) result.push({
1170
+ msgNum: parts[0],
1171
+ uid: parts[1]
1172
+ });
1173
+ }
1174
+ return result;
1175
+ }
1176
+ shouldIncludeMessage(message, options) {
1177
+ if (options?.since && message.date) {
1178
+ if (message.date < options.since) return false;
1179
+ }
1180
+ if (options?.before && message.date) {
1181
+ if (message.date >= options.before) return false;
1182
+ }
1183
+ return true;
1184
+ }
1185
+ mapPOP3Error(error) {
1186
+ if (error instanceof EmailError) return error;
1187
+ if (error instanceof Error) {
1188
+ const message = error.message.toLowerCase();
1189
+ const errorObj = error;
1190
+ if (message.includes("timeout") || errorObj.eventName === "timeout" || errorObj.code === "ETIMEDOUT") return new TimeoutError(error.message, "pop3", error);
1191
+ if (message.includes("connect") || message.includes("econnrefused") || message.includes("enotfound") || message.includes("ehostunreach") || errorObj.eventName === "error" || errorObj.eventName === "close" || errorObj.code === "ECONNREFUSED" || errorObj.code === "ENOTFOUND") return new ConnectionError(error.message, "pop3", error);
1192
+ if (message.includes("auth") || message.includes("login") || message.includes("password") || message.includes("user") || message.includes("-err")) return new AuthenticationError(error.message, "pop3", error);
1193
+ return new EmailError(error.message, "POP3_ERROR", "pop3", error);
1194
+ }
1195
+ return new EmailError(String(error), "POP3_ERROR", "pop3");
1196
+ }
1197
+ };
1198
+ //#endregion
1199
+ //#region src/adapters/smtp.ts
1200
+ var smtp_exports = /* @__PURE__ */ __exportAll({ SMTPAdapter: () => SMTPAdapter });
1201
+ /**
1202
+ * SMTP adapter for sending email via SMTP protocol
1203
+ *
1204
+ * Uses Nodemailer for robust SMTP operations including:
1205
+ * - Plain text and HTML email
1206
+ * - Attachments (files and inline images)
1207
+ * - Multiple recipients (To, CC, BCC)
1208
+ * - Connection pooling
1209
+ * - OAuth2 authentication
1210
+ * - TLS/SSL support
1211
+ */
1212
+ var SMTPAdapter = class extends BaseEmailClient {
1213
+ transporter;
1214
+ options;
1215
+ constructor(options) {
1216
+ super({
1217
+ type: "smtp",
1218
+ debug: options.debug
1219
+ });
1220
+ this.options = options;
1221
+ this.transporter = this.createTransporter();
1222
+ }
1223
+ /**
1224
+ * Create Nodemailer transporter with configuration
1225
+ */
1226
+ createTransporter() {
1227
+ const config = {
1228
+ host: this.options.host,
1229
+ port: this.options.port,
1230
+ secure: this.options.secure ?? false,
1231
+ auth: this.options.auth,
1232
+ tls: this.options.tls,
1233
+ connectionTimeout: this.options.connectionTimeout,
1234
+ greetingTimeout: this.options.greetingTimeout,
1235
+ socketTimeout: this.options.socketTimeout,
1236
+ pool: this.options.pool,
1237
+ maxConnections: this.options.maxConnections,
1238
+ maxMessages: this.options.maxMessages,
1239
+ debug: this.options.debug
1240
+ };
1241
+ return nodemailer.createTransport(config);
1242
+ }
1243
+ getAdapter() {
1244
+ return "smtp";
1245
+ }
1246
+ /**
1247
+ * Send an email message
1248
+ */
1249
+ async send(message, options) {
1250
+ this.validateMessage(message);
1251
+ try {
1252
+ const mailOptions = {
1253
+ from: this.formatEmailAddress(message.from),
1254
+ to: message.to.map((addr) => this.formatEmailAddress(addr)),
1255
+ cc: message.cc?.map((addr) => this.formatEmailAddress(addr)),
1256
+ bcc: message.bcc?.map((addr) => this.formatEmailAddress(addr)),
1257
+ subject: message.subject,
1258
+ text: message.text,
1259
+ html: message.html,
1260
+ replyTo: message.replyTo ? this.formatEmailAddress(message.replyTo) : void 0,
1261
+ inReplyTo: message.inReplyTo,
1262
+ references: message.references,
1263
+ attachments: message.attachments?.map((att) => ({
1264
+ filename: att.filename,
1265
+ content: att.content,
1266
+ path: att.path,
1267
+ contentType: att.contentType,
1268
+ cid: att.contentId,
1269
+ contentDisposition: att.contentDisposition
1270
+ })),
1271
+ headers: message.headers,
1272
+ messageId: message.messageId || options?.messageId,
1273
+ date: message.date || options?.date,
1274
+ encoding: options?.encoding,
1275
+ textEncoding: options?.textEncoding,
1276
+ envelope: options?.envelope,
1277
+ priority: options?.priority,
1278
+ dsn: options?.dsn
1279
+ };
1280
+ this.debug("Sending email", {
1281
+ to: mailOptions.to,
1282
+ subject: message.subject
1283
+ });
1284
+ const sendInfo = await this.transporter.sendMail(mailOptions);
1285
+ return {
1286
+ messageId: sendInfo.messageId,
1287
+ accepted: sendInfo.accepted,
1288
+ rejected: sendInfo.rejected,
1289
+ response: sendInfo.response
1290
+ };
1291
+ } catch (error) {
1292
+ throw this.mapSMTPError(error);
1293
+ }
1294
+ }
1295
+ /**
1296
+ * Format email address for Nodemailer
1297
+ */
1298
+ formatEmailAddress(addr) {
1299
+ if (addr.name) return `"${addr.name.replace(/"/g, "\\\"")}" <${addr.address}>`;
1300
+ return addr.address;
1301
+ }
1302
+ /**
1303
+ * Map Nodemailer/SMTP errors to standard error types
1304
+ */
1305
+ mapSMTPError(error) {
1306
+ if (error instanceof EmailError) return error;
1307
+ if (error instanceof Error) {
1308
+ const message = error.message.toLowerCase();
1309
+ if (message.includes("authentication") || message.includes("invalid login") || message.includes("535")) return new AuthenticationError(error.message, "smtp", error);
1310
+ if (message.includes("timeout") || message.includes("etimedout")) return new TimeoutError(error.message, "smtp", error);
1311
+ if (message.includes("econnrefused") || message.includes("enotfound") || message.includes("connection") || message.includes("ehostunreach")) return new ConnectionError(error.message, "smtp", error);
1312
+ if ("accepted" in error && "rejected" in error) {
1313
+ const errorWithRecipients = error;
1314
+ return new SendError(error.message, errorWithRecipients.accepted || [], errorWithRecipients.rejected || [], "smtp", error);
1315
+ }
1316
+ return new EmailError(error.message, "SMTP_ERROR", "smtp", error);
1317
+ }
1318
+ return new EmailError(String(error), "UNKNOWN_ERROR", "smtp");
1319
+ }
1320
+ /**
1321
+ * Connect to SMTP server (verify connection)
1322
+ */
1323
+ async connect() {
1324
+ try {
1325
+ this.debug("Verifying SMTP connection", { host: this.options.host });
1326
+ await this.transporter.verify();
1327
+ this.connected = true;
1328
+ this.debug("SMTP connection verified");
1329
+ } catch (error) {
1330
+ this.connected = false;
1331
+ throw this.mapSMTPError(error);
1332
+ }
1333
+ }
1334
+ /**
1335
+ * Disconnect from SMTP server
1336
+ */
1337
+ async disconnect() {
1338
+ this.transporter.close();
1339
+ this.connected = false;
1340
+ this.debug("SMTP connection closed");
1341
+ }
1342
+ /**
1343
+ * Get adapter capabilities
1344
+ */
1345
+ async getCapabilities() {
1346
+ return {
1347
+ send: true,
1348
+ receive: false,
1349
+ folders: false,
1350
+ search: false,
1351
+ markRead: false,
1352
+ move: false,
1353
+ delete: false,
1354
+ threads: false,
1355
+ oauth: this.isOAuth2()
1356
+ };
1357
+ }
1358
+ /**
1359
+ * Check if using OAuth2 authentication
1360
+ */
1361
+ isOAuth2() {
1362
+ return this.options.auth !== void 0 && "type" in this.options.auth && this.options.auth.type === "OAuth2";
1363
+ }
1364
+ async fetch(_options) {
1365
+ throw new EmailError("SMTP does not support receiving messages. Use IMAP or POP3 adapter.", "UNSUPPORTED_OPERATION", "smtp");
1366
+ }
1367
+ async getMessage(_messageId) {
1368
+ throw new EmailError("SMTP does not support receiving messages. Use IMAP or POP3 adapter.", "UNSUPPORTED_OPERATION", "smtp");
1369
+ }
1370
+ async listFolders() {
1371
+ throw new EmailError("SMTP does not support folder operations.", "UNSUPPORTED_OPERATION", "smtp");
1372
+ }
1373
+ async selectFolder(_name) {
1374
+ throw new EmailError("SMTP does not support folder operations.", "UNSUPPORTED_OPERATION", "smtp");
1375
+ }
1376
+ async createFolder(_name) {
1377
+ throw new EmailError("SMTP does not support folder operations.", "UNSUPPORTED_OPERATION", "smtp");
1378
+ }
1379
+ async deleteFolder(_name) {
1380
+ throw new EmailError("SMTP does not support folder operations.", "UNSUPPORTED_OPERATION", "smtp");
1381
+ }
1382
+ async markRead(_messageId) {
1383
+ throw new EmailError("SMTP does not support message operations.", "UNSUPPORTED_OPERATION", "smtp");
1384
+ }
1385
+ async markUnread(_messageId) {
1386
+ throw new EmailError("SMTP does not support message operations.", "UNSUPPORTED_OPERATION", "smtp");
1387
+ }
1388
+ async move(_messageId, _folder) {
1389
+ throw new EmailError("SMTP does not support message operations.", "UNSUPPORTED_OPERATION", "smtp");
1390
+ }
1391
+ async copy(_messageId, _folder) {
1392
+ throw new EmailError("SMTP does not support message operations.", "UNSUPPORTED_OPERATION", "smtp");
1393
+ }
1394
+ async delete(_messageId) {
1395
+ throw new EmailError("SMTP does not support message operations.", "UNSUPPORTED_OPERATION", "smtp");
1396
+ }
1397
+ async search(_criteria) {
1398
+ throw new EmailError("SMTP does not support search operations.", "UNSUPPORTED_OPERATION", "smtp");
1399
+ }
1400
+ };
1401
+ //#endregion
1402
+ //#region src/shared/factory.ts
2026
1403
  function isSMTPOptions(opts) {
2027
- return opts.type === "smtp";
1404
+ return opts.type === "smtp";
2028
1405
  }
2029
1406
  function isIMAPOptions(opts) {
2030
- return opts.type === "imap";
1407
+ return opts.type === "imap";
2031
1408
  }
2032
1409
  function isPOP3Options(opts) {
2033
- return opts.type === "pop3";
1410
+ return opts.type === "pop3";
2034
1411
  }
2035
1412
  function isGmailOptions(opts) {
2036
- return opts.type === "gmail";
1413
+ return opts.type === "gmail";
2037
1414
  }
1415
+ /**
1416
+ * Create an email client adapter instance
1417
+ *
1418
+ * @param options - Configuration options for the email client adapter
1419
+ * @returns Promise resolving to an EmailClient instance
1420
+ *
1421
+ * @example
1422
+ * ```typescript
1423
+ * // SMTP adapter
1424
+ * const smtp = await getEmailClient({
1425
+ * type: 'smtp',
1426
+ * host: 'smtp.gmail.com',
1427
+ * port: 587,
1428
+ * auth: { user: 'user@gmail.com', pass: 'password' }
1429
+ * });
1430
+ *
1431
+ * // IMAP adapter
1432
+ * const imap = await getEmailClient({
1433
+ * type: 'imap',
1434
+ * host: 'imap.gmail.com',
1435
+ * port: 993,
1436
+ * secure: true,
1437
+ * auth: { user: 'user@gmail.com', pass: 'password' }
1438
+ * });
1439
+ *
1440
+ * // Gmail adapter
1441
+ * const gmail = await getEmailClient({
1442
+ * type: 'gmail',
1443
+ * auth: {
1444
+ * clientId: 'CLIENT_ID',
1445
+ * clientSecret: 'CLIENT_SECRET',
1446
+ * refreshToken: 'REFRESH_TOKEN'
1447
+ * }
1448
+ * });
1449
+ * ```
1450
+ */
2038
1451
  async function getEmailClient(options) {
2039
- const opts = await loadEnvironmentConfig(options);
2040
- if (isSMTPOptions(opts)) {
2041
- const { SMTPAdapter: SMTPAdapter2 } = await Promise.resolve().then(() => smtp);
2042
- return new SMTPAdapter2(opts);
2043
- }
2044
- if (isIMAPOptions(opts)) {
2045
- const { IMAPAdapter: IMAPAdapter2 } = await Promise.resolve().then(() => imap);
2046
- return new IMAPAdapter2(opts);
2047
- }
2048
- if (isPOP3Options(opts)) {
2049
- const { POP3Adapter: POP3Adapter2 } = await Promise.resolve().then(() => pop3);
2050
- return new POP3Adapter2(opts);
2051
- }
2052
- if (isGmailOptions(opts)) {
2053
- const { GmailAdapter: GmailAdapter2 } = await Promise.resolve().then(() => gmail);
2054
- return new GmailAdapter2(opts);
2055
- }
2056
- throw new Error(
2057
- `Unknown email client type: ${opts.type}`
2058
- );
1452
+ const opts = await loadEnvironmentConfig(options);
1453
+ if (isSMTPOptions(opts)) {
1454
+ const { SMTPAdapter } = await Promise.resolve().then(() => smtp_exports);
1455
+ return new SMTPAdapter(opts);
1456
+ }
1457
+ if (isIMAPOptions(opts)) {
1458
+ const { IMAPAdapter } = await Promise.resolve().then(() => imap_exports);
1459
+ return new IMAPAdapter(opts);
1460
+ }
1461
+ if (isPOP3Options(opts)) {
1462
+ const { POP3Adapter } = await Promise.resolve().then(() => pop3_exports);
1463
+ return new POP3Adapter(opts);
1464
+ }
1465
+ if (isGmailOptions(opts)) {
1466
+ const { GmailAdapter } = await Promise.resolve().then(() => gmail_exports);
1467
+ return new GmailAdapter(opts);
1468
+ }
1469
+ throw new Error(`Unknown email client type: ${opts.type}`);
2059
1470
  }
1471
+ /**
1472
+ * Load configuration from environment variables
1473
+ *
1474
+ * Environment variables follow the pattern: HAVE_EMAIL_*
1475
+ *
1476
+ * Common variables:
1477
+ * - HAVE_EMAIL_TYPE: Adapter type (smtp, imap, pop3, gmail)
1478
+ * - HAVE_EMAIL_HOST: Server host
1479
+ * - HAVE_EMAIL_PORT: Server port
1480
+ * - HAVE_EMAIL_SECURE: Use TLS/SSL (true/false)
1481
+ * - HAVE_EMAIL_USER: Username/email
1482
+ * - HAVE_EMAIL_PASSWORD: Password
1483
+ * - HAVE_EMAIL_DEBUG: Enable debug logging (true/false)
1484
+ *
1485
+ * SMTP-specific:
1486
+ * - HAVE_EMAIL_SMTP_HOST
1487
+ * - HAVE_EMAIL_SMTP_PORT
1488
+ * - HAVE_EMAIL_SMTP_SECURE
1489
+ *
1490
+ * IMAP-specific:
1491
+ * - HAVE_EMAIL_IMAP_HOST
1492
+ * - HAVE_EMAIL_IMAP_PORT
1493
+ * - HAVE_EMAIL_IMAP_SECURE
1494
+ *
1495
+ * Gmail-specific:
1496
+ * - HAVE_EMAIL_GMAIL_CLIENT_ID
1497
+ * - HAVE_EMAIL_GMAIL_CLIENT_SECRET
1498
+ * - HAVE_EMAIL_GMAIL_REFRESH_TOKEN
1499
+ */
2060
1500
  async function loadEnvironmentConfig(options) {
2061
- if (!options.type && process.env.HAVE_EMAIL_TYPE) {
2062
- options = {
2063
- ...options,
2064
- type: process.env.HAVE_EMAIL_TYPE
2065
- };
2066
- }
2067
- if (options.type === "smtp") {
2068
- return loadSMTPEnvironmentConfig(options);
2069
- }
2070
- if (options.type === "imap") {
2071
- return loadIMAPEnvironmentConfig(options);
2072
- }
2073
- if (options.type === "pop3") {
2074
- return loadPOP3EnvironmentConfig(options);
2075
- }
2076
- if (options.type === "gmail") {
2077
- return loadGmailEnvironmentConfig(options);
2078
- }
2079
- return options;
1501
+ if (!options.type && process.env.HAVE_EMAIL_TYPE) options = {
1502
+ ...options,
1503
+ type: process.env.HAVE_EMAIL_TYPE
1504
+ };
1505
+ if (options.type === "smtp") return loadSMTPEnvironmentConfig(options);
1506
+ if (options.type === "imap") return loadIMAPEnvironmentConfig(options);
1507
+ if (options.type === "pop3") return loadPOP3EnvironmentConfig(options);
1508
+ if (options.type === "gmail") return loadGmailEnvironmentConfig(options);
1509
+ return options;
2080
1510
  }
2081
1511
  function loadSMTPEnvironmentConfig(options) {
2082
- return {
2083
- ...options,
2084
- host: options.host || process.env.HAVE_EMAIL_SMTP_HOST || process.env.HAVE_EMAIL_HOST || "",
2085
- port: options.port || Number.parseInt(
2086
- process.env.HAVE_EMAIL_SMTP_PORT || process.env.HAVE_EMAIL_PORT || "587",
2087
- 10
2088
- ),
2089
- secure: options.secure ?? (process.env.HAVE_EMAIL_SMTP_SECURE || process.env.HAVE_EMAIL_SECURE) === "true",
2090
- auth: options.auth || {
2091
- user: process.env.HAVE_EMAIL_USER || process.env.HAVE_EMAIL_SMTP_USER,
2092
- pass: process.env.HAVE_EMAIL_PASSWORD || process.env.HAVE_EMAIL_SMTP_PASSWORD
2093
- },
2094
- debug: options.debug ?? process.env.HAVE_EMAIL_DEBUG === "true"
2095
- };
1512
+ return {
1513
+ ...options,
1514
+ host: options.host || process.env.HAVE_EMAIL_SMTP_HOST || process.env.HAVE_EMAIL_HOST || "",
1515
+ port: options.port || Number.parseInt(process.env.HAVE_EMAIL_SMTP_PORT || process.env.HAVE_EMAIL_PORT || "587", 10),
1516
+ secure: options.secure ?? (process.env.HAVE_EMAIL_SMTP_SECURE || process.env.HAVE_EMAIL_SECURE) === "true",
1517
+ auth: options.auth || {
1518
+ user: process.env.HAVE_EMAIL_USER || process.env.HAVE_EMAIL_SMTP_USER,
1519
+ pass: process.env.HAVE_EMAIL_PASSWORD || process.env.HAVE_EMAIL_SMTP_PASSWORD
1520
+ },
1521
+ debug: options.debug ?? process.env.HAVE_EMAIL_DEBUG === "true"
1522
+ };
2096
1523
  }
2097
1524
  function loadIMAPEnvironmentConfig(options) {
2098
- return {
2099
- ...options,
2100
- host: options.host || process.env.HAVE_EMAIL_IMAP_HOST || process.env.HAVE_EMAIL_HOST || "",
2101
- port: options.port || Number.parseInt(
2102
- process.env.HAVE_EMAIL_IMAP_PORT || process.env.HAVE_EMAIL_PORT || "993",
2103
- 10
2104
- ),
2105
- secure: options.secure ?? (process.env.HAVE_EMAIL_IMAP_SECURE || process.env.HAVE_EMAIL_SECURE) === "true",
2106
- auth: options.auth || {
2107
- user: process.env.HAVE_EMAIL_USER || process.env.HAVE_EMAIL_IMAP_USER,
2108
- pass: process.env.HAVE_EMAIL_PASSWORD || process.env.HAVE_EMAIL_IMAP_PASSWORD
2109
- },
2110
- debug: options.debug ?? process.env.HAVE_EMAIL_DEBUG === "true"
2111
- };
1525
+ return {
1526
+ ...options,
1527
+ host: options.host || process.env.HAVE_EMAIL_IMAP_HOST || process.env.HAVE_EMAIL_HOST || "",
1528
+ port: options.port || Number.parseInt(process.env.HAVE_EMAIL_IMAP_PORT || process.env.HAVE_EMAIL_PORT || "993", 10),
1529
+ secure: options.secure ?? (process.env.HAVE_EMAIL_IMAP_SECURE || process.env.HAVE_EMAIL_SECURE) === "true",
1530
+ auth: options.auth || {
1531
+ user: process.env.HAVE_EMAIL_USER || process.env.HAVE_EMAIL_IMAP_USER,
1532
+ pass: process.env.HAVE_EMAIL_PASSWORD || process.env.HAVE_EMAIL_IMAP_PASSWORD
1533
+ },
1534
+ debug: options.debug ?? process.env.HAVE_EMAIL_DEBUG === "true"
1535
+ };
2112
1536
  }
2113
1537
  function loadPOP3EnvironmentConfig(options) {
2114
- return {
2115
- ...options,
2116
- host: options.host || process.env.HAVE_EMAIL_POP3_HOST || process.env.HAVE_EMAIL_HOST || "",
2117
- port: options.port || Number.parseInt(
2118
- process.env.HAVE_EMAIL_POP3_PORT || process.env.HAVE_EMAIL_PORT || "995",
2119
- 10
2120
- ),
2121
- secure: options.secure ?? (process.env.HAVE_EMAIL_POP3_SECURE || process.env.HAVE_EMAIL_SECURE) === "true",
2122
- auth: options.auth || {
2123
- user: process.env.HAVE_EMAIL_USER || process.env.HAVE_EMAIL_POP3_USER,
2124
- pass: process.env.HAVE_EMAIL_PASSWORD || process.env.HAVE_EMAIL_POP3_PASSWORD
2125
- },
2126
- debug: options.debug ?? process.env.HAVE_EMAIL_DEBUG === "true"
2127
- };
1538
+ return {
1539
+ ...options,
1540
+ host: options.host || process.env.HAVE_EMAIL_POP3_HOST || process.env.HAVE_EMAIL_HOST || "",
1541
+ port: options.port || Number.parseInt(process.env.HAVE_EMAIL_POP3_PORT || process.env.HAVE_EMAIL_PORT || "995", 10),
1542
+ secure: options.secure ?? (process.env.HAVE_EMAIL_POP3_SECURE || process.env.HAVE_EMAIL_SECURE) === "true",
1543
+ auth: options.auth || {
1544
+ user: process.env.HAVE_EMAIL_USER || process.env.HAVE_EMAIL_POP3_USER,
1545
+ pass: process.env.HAVE_EMAIL_PASSWORD || process.env.HAVE_EMAIL_POP3_PASSWORD
1546
+ },
1547
+ debug: options.debug ?? process.env.HAVE_EMAIL_DEBUG === "true"
1548
+ };
2128
1549
  }
2129
1550
  function loadGmailEnvironmentConfig(options) {
2130
- return {
2131
- ...options,
2132
- auth: options.auth || {
2133
- clientId: process.env.HAVE_EMAIL_GMAIL_CLIENT_ID || "",
2134
- clientSecret: process.env.HAVE_EMAIL_GMAIL_CLIENT_SECRET || "",
2135
- refreshToken: process.env.HAVE_EMAIL_GMAIL_REFRESH_TOKEN || "",
2136
- accessToken: process.env.HAVE_EMAIL_GMAIL_ACCESS_TOKEN
2137
- },
2138
- userId: options.userId || process.env.HAVE_EMAIL_GMAIL_USER_ID || "me",
2139
- debug: options.debug ?? process.env.HAVE_EMAIL_DEBUG === "true"
2140
- };
1551
+ return {
1552
+ ...options,
1553
+ auth: options.auth || {
1554
+ clientId: process.env.HAVE_EMAIL_GMAIL_CLIENT_ID || "",
1555
+ clientSecret: process.env.HAVE_EMAIL_GMAIL_CLIENT_SECRET || "",
1556
+ refreshToken: process.env.HAVE_EMAIL_GMAIL_REFRESH_TOKEN || "",
1557
+ accessToken: process.env.HAVE_EMAIL_GMAIL_ACCESS_TOKEN
1558
+ },
1559
+ userId: options.userId || process.env.HAVE_EMAIL_GMAIL_USER_ID || "me",
1560
+ debug: options.debug ?? process.env.HAVE_EMAIL_DEBUG === "true"
1561
+ };
2141
1562
  }
2142
- export {
2143
- AttachmentError,
2144
- AuthenticationError,
2145
- AuthorizationError,
2146
- BaseEmailClient,
2147
- ConnectionError,
2148
- EmailError,
2149
- FolderExistsError,
2150
- FolderNotFoundError,
2151
- GmailAdapter,
2152
- IMAPAdapter,
2153
- InvalidMessageError,
2154
- MessageNotFoundError,
2155
- POP3Adapter,
2156
- SMTPAdapter,
2157
- SendError,
2158
- TimeoutError,
2159
- getEmailClient,
2160
- isGmailOptions,
2161
- isIMAPOptions,
2162
- isPOP3Options,
2163
- isSMTPOptions
2164
- };
2165
- //# sourceMappingURL=index.js.map
1563
+ //#endregion
1564
+ export { AttachmentError, AuthenticationError, AuthorizationError, BaseEmailClient, ConnectionError, EmailError, FolderExistsError, FolderNotFoundError, GmailAdapter, IMAPAdapter, InvalidMessageError, MessageNotFoundError, POP3Adapter, SMTPAdapter, SendError, TimeoutError, getEmailClient, isGmailOptions, isIMAPOptions, isPOP3Options, isSMTPOptions };
1565
+
1566
+ //# sourceMappingURL=index.js.map