@happyvertical/email 0.74.8
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/AGENT.md +34 -0
- package/LICENSE +7 -0
- package/dist/adapters/gmail.d.ts +43 -0
- package/dist/adapters/gmail.d.ts.map +1 -0
- package/dist/adapters/imap.d.ts +114 -0
- package/dist/adapters/imap.d.ts.map +1 -0
- package/dist/adapters/pop3.d.ts +44 -0
- package/dist/adapters/pop3.d.ts.map +1 -0
- package/dist/adapters/smtp.d.ts +64 -0
- package/dist/adapters/smtp.d.ts.map +1 -0
- package/dist/cli/claude-context.d.ts +3 -0
- package/dist/cli/claude-context.d.ts.map +1 -0
- package/dist/cli/claude-context.js +21 -0
- package/dist/cli/claude-context.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2165 -0
- package/dist/index.js.map +1 -0
- package/dist/shared/base.d.ts +41 -0
- package/dist/shared/base.d.ts.map +1 -0
- package/dist/shared/errors.d.ts +46 -0
- package/dist/shared/errors.d.ts.map +1 -0
- package/dist/shared/factory.d.ts +43 -0
- package/dist/shared/factory.d.ts.map +1 -0
- package/dist/shared/types.d.ts +242 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/metadata.json +38 -0
- package/package.json +76 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2165 @@
|
|
|
1
|
+
import { google } from "googleapis";
|
|
2
|
+
import { simpleParser } from "mailparser";
|
|
3
|
+
import { createLogger } from "@happyvertical/logger";
|
|
4
|
+
import { ImapFlow } from "imapflow";
|
|
5
|
+
import Pop3Command from "node-pop3";
|
|
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" }));
|
|
2026
|
+
function isSMTPOptions(opts) {
|
|
2027
|
+
return opts.type === "smtp";
|
|
2028
|
+
}
|
|
2029
|
+
function isIMAPOptions(opts) {
|
|
2030
|
+
return opts.type === "imap";
|
|
2031
|
+
}
|
|
2032
|
+
function isPOP3Options(opts) {
|
|
2033
|
+
return opts.type === "pop3";
|
|
2034
|
+
}
|
|
2035
|
+
function isGmailOptions(opts) {
|
|
2036
|
+
return opts.type === "gmail";
|
|
2037
|
+
}
|
|
2038
|
+
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
|
+
);
|
|
2059
|
+
}
|
|
2060
|
+
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;
|
|
2080
|
+
}
|
|
2081
|
+
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
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
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
|
+
};
|
|
2112
|
+
}
|
|
2113
|
+
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
|
+
};
|
|
2128
|
+
}
|
|
2129
|
+
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
|
+
};
|
|
2141
|
+
}
|
|
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
|