mikromail 0.0.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.
@@ -0,0 +1,632 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/SMTPClient.ts
31
+ var SMTPClient_exports = {};
32
+ __export(SMTPClient_exports, {
33
+ SMTPClient: () => SMTPClient
34
+ });
35
+ module.exports = __toCommonJS(SMTPClient_exports);
36
+ var import_node_buffer = require("buffer");
37
+ var import_node_crypto = __toESM(require("crypto"));
38
+ var import_node_net = __toESM(require("net"));
39
+ var import_node_os = __toESM(require("os"));
40
+ var import_node_tls = __toESM(require("tls"));
41
+
42
+ // src/utils/index.ts
43
+ var import_node_dns = require("dns");
44
+ function encodeQuotedPrintable(text) {
45
+ let result = text.replace(/\r?\n/g, "\r\n");
46
+ result = result.replace(/=/g, "=3D");
47
+ const utf8Bytes = new TextEncoder().encode(result);
48
+ let encoded = "";
49
+ let lineLength = 0;
50
+ for (let i = 0; i < utf8Bytes.length; i++) {
51
+ const byte = utf8Bytes[i];
52
+ let chunk = "";
53
+ if (byte >= 33 && byte <= 126 && byte !== 61 || byte === 32) {
54
+ chunk = String.fromCharCode(byte);
55
+ } else if (byte === 13 || byte === 10) {
56
+ chunk = String.fromCharCode(byte);
57
+ if (byte === 10) {
58
+ lineLength = 0;
59
+ }
60
+ } else {
61
+ const hex = byte.toString(16).toUpperCase();
62
+ chunk = `=${hex.length < 2 ? `0${hex}` : hex}`;
63
+ }
64
+ if (lineLength + chunk.length > 75 && !(byte === 13 || byte === 10)) {
65
+ encoded += "=\r\n";
66
+ lineLength = 0;
67
+ }
68
+ encoded += chunk;
69
+ lineLength += chunk.length;
70
+ }
71
+ return encoded;
72
+ }
73
+ function validateEmail(email) {
74
+ try {
75
+ const [localPart, domain] = email.split("@");
76
+ if (!localPart || localPart.length > 64) return false;
77
+ if (localPart.startsWith(".") || localPart.endsWith(".") || localPart.includes(".."))
78
+ return false;
79
+ if (!/^[a-zA-Z0-9!#$%&'*+\-/=?^_`{|}~.]+$/.test(localPart)) return false;
80
+ if (!domain || domain.length > 255) return false;
81
+ if (domain.startsWith("[") && domain.endsWith("]")) {
82
+ const ipContent = domain.slice(1, -1);
83
+ if (ipContent.startsWith("IPv6:")) return true;
84
+ const ipv4Regex = /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/;
85
+ return ipv4Regex.test(ipContent);
86
+ }
87
+ if (domain.startsWith(".") || domain.endsWith(".") || domain.includes(".."))
88
+ return false;
89
+ const domainParts = domain.split(".");
90
+ if (domainParts.length < 2 || domainParts[domainParts.length - 1].length < 2)
91
+ return false;
92
+ for (const part of domainParts) {
93
+ if (!part || part.length > 63) return false;
94
+ if (!/^[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?$/.test(part)) return false;
95
+ }
96
+ return true;
97
+ } catch (_error) {
98
+ return false;
99
+ }
100
+ }
101
+
102
+ // src/SMTPClient.ts
103
+ var SMTPClient = class {
104
+ config;
105
+ socket;
106
+ connected;
107
+ lastCommand;
108
+ serverCapabilities;
109
+ secureMode;
110
+ retryCount;
111
+ constructor(config) {
112
+ this.config = {
113
+ host: config.host,
114
+ user: config.user,
115
+ password: config.password,
116
+ port: config.port ?? (config.secure ? 465 : 587),
117
+ secure: config.secure ?? true,
118
+ debug: config.debug ?? false,
119
+ timeout: config.timeout ?? 1e4,
120
+ clientName: config.clientName ?? import_node_os.default.hostname(),
121
+ maxRetries: config.maxRetries ?? 3,
122
+ retryDelay: config.retryDelay ?? 1e3,
123
+ skipAuthentication: config.skipAuthentication || false
124
+ };
125
+ this.socket = null;
126
+ this.connected = false;
127
+ this.lastCommand = "";
128
+ this.serverCapabilities = [];
129
+ this.secureMode = this.config.secure;
130
+ this.retryCount = 0;
131
+ }
132
+ /**
133
+ * Log debug messages if debug mode is enabled
134
+ */
135
+ log(message, isError = false) {
136
+ if (this.config.debug) {
137
+ const prefix = isError ? "SMTP ERROR: " : "SMTP: ";
138
+ console.log(`${prefix}${message}`);
139
+ }
140
+ }
141
+ /**
142
+ * Connect to the SMTP server
143
+ */
144
+ async connect() {
145
+ return new Promise((resolve, reject) => {
146
+ const connectionTimeout = setTimeout(() => {
147
+ reject(new Error(`Connection timeout after ${this.config.timeout}ms`));
148
+ this.socket?.destroy();
149
+ }, this.config.timeout);
150
+ try {
151
+ if (this.config.secure) {
152
+ this.createTLSConnection(connectionTimeout, resolve, reject);
153
+ } else {
154
+ this.createPlainConnection(connectionTimeout, resolve, reject);
155
+ }
156
+ } catch (error) {
157
+ clearTimeout(connectionTimeout);
158
+ this.log(`Failed to create socket: ${error.message}`, true);
159
+ reject(error);
160
+ }
161
+ });
162
+ }
163
+ /**
164
+ * Create a secure TLS connection
165
+ */
166
+ createTLSConnection(connectionTimeout, resolve, reject) {
167
+ this.socket = import_node_tls.default.connect({
168
+ host: this.config.host,
169
+ port: this.config.port,
170
+ rejectUnauthorized: true,
171
+ // Always validate TLS certificates
172
+ minVersion: "TLSv1.2",
173
+ // Enforce TLS 1.2 or higher
174
+ ciphers: "HIGH:!aNULL:!MD5:!RC4"
175
+ });
176
+ this.setupSocketEventHandlers(connectionTimeout, resolve, reject);
177
+ }
178
+ /**
179
+ * Create a plain socket connection (for later STARTTLS upgrade)
180
+ */
181
+ createPlainConnection(connectionTimeout, resolve, reject) {
182
+ this.socket = import_node_net.default.createConnection({
183
+ host: this.config.host,
184
+ port: this.config.port
185
+ });
186
+ this.setupSocketEventHandlers(connectionTimeout, resolve, reject);
187
+ }
188
+ /**
189
+ * Set up common socket event handlers
190
+ */
191
+ setupSocketEventHandlers(connectionTimeout, resolve, reject) {
192
+ if (!this.socket) return;
193
+ this.socket.once("error", (err) => {
194
+ clearTimeout(connectionTimeout);
195
+ this.log(`Connection error: ${err.message}`, true);
196
+ reject(new Error(`SMTP connection error: ${err.message}`));
197
+ });
198
+ this.socket.once("connect", () => {
199
+ this.log("Connected to SMTP server");
200
+ clearTimeout(connectionTimeout);
201
+ this.socket.once("data", (data) => {
202
+ const greeting = data.toString().trim();
203
+ this.log(`Server greeting: ${greeting}`);
204
+ if (greeting.startsWith("220")) {
205
+ this.connected = true;
206
+ this.secureMode = this.config.secure;
207
+ resolve();
208
+ } else {
209
+ reject(new Error(`Unexpected server greeting: ${greeting}`));
210
+ this.socket.destroy();
211
+ }
212
+ });
213
+ });
214
+ this.socket.once("close", (hadError) => {
215
+ if (this.connected) {
216
+ this.log(`Connection closed${hadError ? " with error" : ""}`);
217
+ } else {
218
+ clearTimeout(connectionTimeout);
219
+ reject(new Error("Connection closed before initialization completed"));
220
+ }
221
+ this.connected = false;
222
+ });
223
+ }
224
+ /**
225
+ * Upgrade connection to TLS using STARTTLS
226
+ */
227
+ async upgradeToTLS() {
228
+ if (!this.socket || this.secureMode) return;
229
+ return new Promise((resolve, reject) => {
230
+ const plainSocket = this.socket;
231
+ const tlsOptions = {
232
+ socket: plainSocket,
233
+ host: this.config.host,
234
+ rejectUnauthorized: true,
235
+ minVersion: "TLSv1.2",
236
+ ciphers: "HIGH:!aNULL:!MD5:!RC4"
237
+ };
238
+ const tlsSocket = import_node_tls.default.connect(tlsOptions);
239
+ tlsSocket.once("error", (err) => {
240
+ this.log(`TLS upgrade error: ${err.message}`, true);
241
+ reject(new Error(`STARTTLS error: ${err.message}`));
242
+ });
243
+ tlsSocket.once("secureConnect", () => {
244
+ this.log("Connection upgraded to TLS");
245
+ if (tlsSocket.authorized) {
246
+ this.socket = tlsSocket;
247
+ this.secureMode = true;
248
+ resolve();
249
+ } else {
250
+ reject(
251
+ new Error(
252
+ `TLS certificate verification failed: ${tlsSocket.authorizationError}`
253
+ )
254
+ );
255
+ }
256
+ });
257
+ });
258
+ }
259
+ /**
260
+ * Send an SMTP command and await response
261
+ */
262
+ async sendCommand(command, expectedCode, timeout = this.config.timeout) {
263
+ if (!this.socket || !this.connected) {
264
+ throw new Error("Not connected to SMTP server");
265
+ }
266
+ return new Promise((resolve, reject) => {
267
+ const commandTimeout = setTimeout(() => {
268
+ this.socket?.removeListener("data", onData);
269
+ reject(new Error(`Command timeout after ${timeout}ms: ${command}`));
270
+ }, timeout);
271
+ let responseData = "";
272
+ const onData = (chunk) => {
273
+ responseData += chunk.toString();
274
+ const lines = responseData.split("\r\n");
275
+ if (lines.length > 0 && lines[lines.length - 1] === "") {
276
+ const lastLine = lines[lines.length - 2] || "";
277
+ const matches = /^(\d{3})(.?)/.exec(lastLine);
278
+ if (matches?.[1] && matches[2] !== "-") {
279
+ this.socket?.removeListener("data", onData);
280
+ clearTimeout(commandTimeout);
281
+ this.log(`SMTP Response: ${responseData.trim()}`);
282
+ if (matches[1] === expectedCode.toString()) {
283
+ resolve(responseData.trim());
284
+ } else {
285
+ reject(new Error(`SMTP Error: ${responseData.trim()}`));
286
+ }
287
+ }
288
+ }
289
+ };
290
+ this.socket.on("data", onData);
291
+ if (command.startsWith("AUTH PLAIN") || command.startsWith("AUTH LOGIN") || this.lastCommand === "AUTH LOGIN" && !command.startsWith("AUTH")) {
292
+ this.log("SMTP Command: [Credentials hidden]");
293
+ } else {
294
+ this.log(`SMTP Command: ${command}`);
295
+ }
296
+ this.lastCommand = command;
297
+ this.socket.write(`${command}\r
298
+ `);
299
+ });
300
+ }
301
+ /**
302
+ * Parse EHLO response to determine server capabilities
303
+ */
304
+ parseCapabilities(ehloResponse) {
305
+ const lines = ehloResponse.split("\r\n");
306
+ this.serverCapabilities = [];
307
+ for (let i = 1; i < lines.length; i++) {
308
+ const line = lines[i];
309
+ if (line.match(/^\d{3}/) && line.charAt(3) === " ") {
310
+ const capability = line.substr(4).toUpperCase();
311
+ this.serverCapabilities.push(capability);
312
+ }
313
+ }
314
+ this.log(`Server capabilities: ${this.serverCapabilities.join(", ")}`);
315
+ }
316
+ /**
317
+ * Determine the best authentication method supported by the server
318
+ */
319
+ getBestAuthMethod() {
320
+ const capabilities = this.serverCapabilities.map(
321
+ (cap) => cap.split(" ")[0]
322
+ );
323
+ if (capabilities.includes("AUTH")) {
324
+ const authLine = this.serverCapabilities.find(
325
+ (cap) => cap.startsWith("AUTH ")
326
+ );
327
+ if (authLine) {
328
+ const methods = authLine.split(" ").slice(1);
329
+ if (methods.includes("CRAM-MD5")) return "CRAM-MD5";
330
+ if (methods.includes("LOGIN")) return "LOGIN";
331
+ if (methods.includes("PLAIN")) return "PLAIN";
332
+ }
333
+ }
334
+ return "PLAIN";
335
+ }
336
+ /**
337
+ * Authenticate with the SMTP server using the best available method
338
+ */
339
+ async authenticate() {
340
+ const authMethod = this.getBestAuthMethod();
341
+ switch (authMethod) {
342
+ case "CRAM-MD5":
343
+ await this.authenticateCramMD5();
344
+ break;
345
+ case "LOGIN":
346
+ await this.authenticateLogin();
347
+ break;
348
+ default:
349
+ await this.authenticatePlain();
350
+ break;
351
+ }
352
+ }
353
+ /**
354
+ * Authenticate using PLAIN method
355
+ */
356
+ async authenticatePlain() {
357
+ const authPlain = import_node_buffer.Buffer.from(
358
+ `\0${this.config.user}\0${this.config.password}`
359
+ ).toString("base64");
360
+ await this.sendCommand(`AUTH PLAIN ${authPlain}`, 235);
361
+ }
362
+ /**
363
+ * Authenticate using LOGIN method
364
+ */
365
+ async authenticateLogin() {
366
+ await this.sendCommand("AUTH LOGIN", 334);
367
+ await this.sendCommand(
368
+ import_node_buffer.Buffer.from(this.config.user).toString("base64"),
369
+ 334
370
+ );
371
+ await this.sendCommand(
372
+ import_node_buffer.Buffer.from(this.config.password).toString("base64"),
373
+ 235
374
+ );
375
+ }
376
+ /**
377
+ * Authenticate using CRAM-MD5 method
378
+ */
379
+ async authenticateCramMD5() {
380
+ const response = await this.sendCommand("AUTH CRAM-MD5", 334);
381
+ const challenge = import_node_buffer.Buffer.from(response.substr(4), "base64").toString(
382
+ "utf8"
383
+ );
384
+ const hmac = import_node_crypto.default.createHmac("md5", this.config.password);
385
+ hmac.update(challenge);
386
+ const digest = hmac.digest("hex");
387
+ const cramResponse = `${this.config.user} ${digest}`;
388
+ const encodedResponse = import_node_buffer.Buffer.from(cramResponse).toString("base64");
389
+ await this.sendCommand(encodedResponse, 235);
390
+ }
391
+ /**
392
+ * Generate a unique message ID
393
+ */
394
+ generateMessageId() {
395
+ const random = import_node_crypto.default.randomBytes(16).toString("hex");
396
+ const domain = this.config.user.split("@")[1] || "localhost";
397
+ return `<${random}@${domain}>`;
398
+ }
399
+ /**
400
+ * Generate MIME boundary for multipart messages
401
+ */
402
+ generateBoundary() {
403
+ return `----=_NextPart_${import_node_crypto.default.randomBytes(12).toString("hex")}`;
404
+ }
405
+ /**
406
+ * Encode string according to RFC 2047 for headers with non-ASCII characters
407
+ */
408
+ encodeHeaderValue(value) {
409
+ if (/^[\x00-\x7F]*$/.test(value)) {
410
+ return value;
411
+ }
412
+ return `=?UTF-8?Q?${// biome-ignore lint/suspicious/noControlCharactersInRegex: <explanation>
413
+ value.replace(/[^\x00-\x7F]/g, (c) => {
414
+ const hex = c.charCodeAt(0).toString(16).toUpperCase();
415
+ return `=${hex.length < 2 ? `0${hex}` : hex}`;
416
+ })}?=`;
417
+ }
418
+ /**
419
+ * Sanitize and encode header value to prevent injection and handle internationalization
420
+ */
421
+ sanitizeHeader(value) {
422
+ const sanitized = value.replace(/[\r\n]+/g, " ");
423
+ return this.encodeHeaderValue(sanitized);
424
+ }
425
+ /**
426
+ * Create email headers with proper sanitization
427
+ */
428
+ createEmailHeaders(options) {
429
+ const messageId = this.generateMessageId();
430
+ const date = (/* @__PURE__ */ new Date()).toUTCString();
431
+ const from = options.from || this.config.user;
432
+ const { to } = options;
433
+ const headers = [
434
+ `From: ${this.sanitizeHeader(from)}`,
435
+ `To: ${this.sanitizeHeader(to)}`,
436
+ `Subject: ${this.sanitizeHeader(options.subject)}`,
437
+ `Message-ID: ${messageId}`,
438
+ `Date: ${date}`,
439
+ "MIME-Version: 1.0"
440
+ ];
441
+ if (options.cc) {
442
+ const cc = Array.isArray(options.cc) ? options.cc.join(", ") : options.cc;
443
+ headers.push(`Cc: ${this.sanitizeHeader(cc)}`);
444
+ }
445
+ if (options.replyTo) {
446
+ headers.push(`Reply-To: ${this.sanitizeHeader(options.replyTo)}`);
447
+ }
448
+ if (options.headers) {
449
+ for (const [name, value] of Object.entries(options.headers)) {
450
+ if (!/^[a-zA-Z0-9-]+$/.test(name)) continue;
451
+ if (/^(from|to|cc|bcc|subject|date|message-id)$/i.test(name)) continue;
452
+ headers.push(`${name}: ${this.sanitizeHeader(value)}`);
453
+ }
454
+ }
455
+ return headers;
456
+ }
457
+ /**
458
+ * Create a multipart email with text and HTML parts
459
+ */
460
+ createMultipartEmail(options) {
461
+ const { text, html } = options;
462
+ const headers = this.createEmailHeaders(options);
463
+ const boundary = this.generateBoundary();
464
+ if (html && text) {
465
+ headers.push(
466
+ `Content-Type: multipart/alternative; boundary="${boundary}"`
467
+ );
468
+ return `${headers.join("\r\n")}\r
469
+ \r
470
+ --${boundary}\r
471
+ Content-Type: text/plain; charset=utf-8\r
472
+ Content-Transfer-Encoding: quoted-printable\r
473
+ \r
474
+ ${encodeQuotedPrintable(text || "")}\r
475
+ \r
476
+ --${boundary}\r
477
+ Content-Type: text/html; charset=utf-8\r
478
+ Content-Transfer-Encoding: quoted-printable\r
479
+ \r
480
+ ${encodeQuotedPrintable(html || "")}\r
481
+ \r
482
+ --${boundary}--\r
483
+ `;
484
+ }
485
+ if (html) {
486
+ headers.push("Content-Type: text/html; charset=utf-8");
487
+ headers.push("Content-Transfer-Encoding: quoted-printable");
488
+ return `${headers.join("\r\n")}\r
489
+ \r
490
+ ${encodeQuotedPrintable(html)}`;
491
+ }
492
+ headers.push("Content-Type: text/plain; charset=utf-8");
493
+ headers.push("Content-Transfer-Encoding: quoted-printable");
494
+ return `${headers.join("\r\n")}\r
495
+ \r
496
+ ${encodeQuotedPrintable(text || "")}`;
497
+ }
498
+ /**
499
+ * Perform full SMTP handshake, including STARTTLS if needed
500
+ */
501
+ async smtpHandshake() {
502
+ const ehloResponse = await this.sendCommand(
503
+ `EHLO ${this.config.clientName}`,
504
+ 250
505
+ );
506
+ this.parseCapabilities(ehloResponse);
507
+ if (!this.secureMode && this.serverCapabilities.includes("STARTTLS")) {
508
+ await this.sendCommand("STARTTLS", 220);
509
+ await this.upgradeToTLS();
510
+ const secureEhloResponse = await this.sendCommand(
511
+ `EHLO ${this.config.clientName}`,
512
+ 250
513
+ );
514
+ this.parseCapabilities(secureEhloResponse);
515
+ }
516
+ if (!this.config.skipAuthentication) {
517
+ await this.authenticate();
518
+ } else {
519
+ this.log("Authentication skipped (testing mode)");
520
+ }
521
+ }
522
+ /**
523
+ * Send an email with retry capability
524
+ */
525
+ async sendEmail(options) {
526
+ const from = options.from || this.config.user;
527
+ const { to, subject } = options;
528
+ const text = options.text || "";
529
+ const html = options.html || "";
530
+ if (!from || !to || !subject || !text && !html) {
531
+ return {
532
+ success: false,
533
+ error: "Missing required email parameters (from, to, subject, and either text or html)"
534
+ };
535
+ }
536
+ if (!validateEmail(from) || !validateEmail(to)) {
537
+ return {
538
+ success: false,
539
+ error: "Invalid email address format"
540
+ };
541
+ }
542
+ for (this.retryCount = 0; this.retryCount <= this.config.maxRetries; this.retryCount++) {
543
+ try {
544
+ if (this.retryCount > 0) {
545
+ this.log(
546
+ `Retrying email send (attempt ${this.retryCount} of ${this.config.maxRetries})...`
547
+ );
548
+ await new Promise(
549
+ (resolve) => setTimeout(resolve, this.config.retryDelay)
550
+ );
551
+ }
552
+ if (!this.connected) {
553
+ await this.connect();
554
+ await this.smtpHandshake();
555
+ }
556
+ await this.sendCommand(`MAIL FROM:<${from}>`, 250);
557
+ await this.sendCommand(`RCPT TO:<${to}>`, 250);
558
+ if (options.cc) {
559
+ const ccList = Array.isArray(options.cc) ? options.cc : [options.cc];
560
+ for (const cc of ccList) {
561
+ if (validateEmail(cc)) {
562
+ await this.sendCommand(`RCPT TO:<${cc}>`, 250);
563
+ }
564
+ }
565
+ }
566
+ if (options.bcc) {
567
+ const bccList = Array.isArray(options.bcc) ? options.bcc : [options.bcc];
568
+ for (const bcc of bccList) {
569
+ if (validateEmail(bcc)) {
570
+ await this.sendCommand(`RCPT TO:<${bcc}>`, 250);
571
+ }
572
+ }
573
+ }
574
+ await this.sendCommand("DATA", 354);
575
+ const emailContent = this.createMultipartEmail(options);
576
+ await this.sendCommand(`${emailContent}\r
577
+ .`, 250);
578
+ const messageIdMatch = /Message-ID: (.*)/i.exec(emailContent);
579
+ const messageId = messageIdMatch ? messageIdMatch[1].trim() : void 0;
580
+ return {
581
+ success: true,
582
+ messageId,
583
+ message: "Email sent successfully"
584
+ };
585
+ } catch (error) {
586
+ const errorMessage = error.message;
587
+ this.log(`Error sending email: ${errorMessage}`, true);
588
+ const isPermanentError = errorMessage.includes("5.") || // 5xx SMTP errors are permanent
589
+ errorMessage.includes("Authentication failed") || errorMessage.includes("certificate");
590
+ if (isPermanentError || this.retryCount >= this.config.maxRetries) {
591
+ return {
592
+ success: false,
593
+ error: errorMessage
594
+ };
595
+ }
596
+ try {
597
+ if (this.connected) {
598
+ await this.sendCommand("RSET", 250);
599
+ }
600
+ } catch (_error) {
601
+ }
602
+ this.socket?.end();
603
+ this.connected = false;
604
+ }
605
+ }
606
+ return {
607
+ success: false,
608
+ error: "Maximum retry count exceeded"
609
+ };
610
+ }
611
+ /**
612
+ * Close the connection gracefully
613
+ */
614
+ async close() {
615
+ try {
616
+ if (this.connected) {
617
+ await this.sendCommand("QUIT", 221);
618
+ }
619
+ } catch (e) {
620
+ this.log(`Error during QUIT: ${e.message}`, true);
621
+ } finally {
622
+ if (this.socket) {
623
+ this.socket.end();
624
+ this.connected = false;
625
+ }
626
+ }
627
+ }
628
+ };
629
+ // Annotate the CommonJS export names for ESM import in node:
630
+ 0 && (module.exports = {
631
+ SMTPClient
632
+ });
@@ -0,0 +1,7 @@
1
+ import {
2
+ SMTPClient
3
+ } from "./chunk-TCYL3UFZ.mjs";
4
+ import "./chunk-UDLJWUFN.mjs";
5
+ export {
6
+ SMTPClient
7
+ };
@@ -0,0 +1,13 @@
1
+ // src/errors/index.ts
2
+ var ValidationError = class extends Error {
3
+ constructor(message) {
4
+ super();
5
+ this.name = "ValidationError";
6
+ this.message = message;
7
+ this.cause = { statusCode: 400 };
8
+ }
9
+ };
10
+
11
+ export {
12
+ ValidationError
13
+ };