bouncevalidator 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,2229 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // bin/cli.ts
27
+ var import_fs = __toESM(require("fs"));
28
+ var import_readline = __toESM(require("readline"));
29
+
30
+ // src/utils/normalize.ts
31
+ function normalizeEmail(email, options = {}) {
32
+ let normalized = email.trim().toLowerCase();
33
+ const atIndex = normalized.indexOf("@");
34
+ if (atIndex === -1) {
35
+ return normalized;
36
+ }
37
+ let username = normalized.slice(0, atIndex);
38
+ const domain = normalized.slice(atIndex + 1);
39
+ if (options.removePlusAddressing) {
40
+ const plusIndex = username.indexOf("+");
41
+ if (plusIndex !== -1) {
42
+ username = username.slice(0, plusIndex);
43
+ }
44
+ }
45
+ if (options.removeDots) {
46
+ const gmailDomains = ["gmail.com", "googlemail.com"];
47
+ if (gmailDomains.includes(domain)) {
48
+ username = username.replace(/\./g, "");
49
+ }
50
+ }
51
+ return `${username}@${domain}`;
52
+ }
53
+ function extractParts(email) {
54
+ const atIndex = email.indexOf("@");
55
+ if (atIndex === -1 || atIndex === 0 || atIndex === email.length - 1) {
56
+ return null;
57
+ }
58
+ if (email.indexOf("@", atIndex + 1) !== -1) {
59
+ return null;
60
+ }
61
+ return {
62
+ username: email.slice(0, atIndex),
63
+ domain: email.slice(atIndex + 1)
64
+ };
65
+ }
66
+ function cleanDomain(domain) {
67
+ let cleaned = domain.trim().toLowerCase();
68
+ if (cleaned.endsWith(".")) {
69
+ cleaned = cleaned.slice(0, -1);
70
+ }
71
+ return cleaned;
72
+ }
73
+
74
+ // src/utils/suggestions.ts
75
+ var domainTypos = {
76
+ // Gmail typos
77
+ "gmial.com": "gmail.com",
78
+ "gmal.com": "gmail.com",
79
+ "gamil.com": "gmail.com",
80
+ "gnail.com": "gmail.com",
81
+ "gmaill.com": "gmail.com",
82
+ "gmali.com": "gmail.com",
83
+ "gmai.com": "gmail.com",
84
+ "gmail.co": "gmail.com",
85
+ "gmail.om": "gmail.com",
86
+ "gmail.cm": "gmail.com",
87
+ "gmail.cim": "gmail.com",
88
+ "gmail.con": "gmail.com",
89
+ "gmail.vom": "gmail.com",
90
+ "gmail.xom": "gmail.com",
91
+ "gmail.comm": "gmail.com",
92
+ "gmail.ocm": "gmail.com",
93
+ "gmaul.com": "gmail.com",
94
+ "gmeil.com": "gmail.com",
95
+ "gmsil.com": "gmail.com",
96
+ "gmqil.com": "gmail.com",
97
+ "gemail.com": "gmail.com",
98
+ "g]mail.com": "gmail.com",
99
+ "fmail.com": "gmail.com",
100
+ // Yahoo typos
101
+ "yaho.com": "yahoo.com",
102
+ "yahooo.com": "yahoo.com",
103
+ "yhoo.com": "yahoo.com",
104
+ "yhaoo.com": "yahoo.com",
105
+ "yaoo.com": "yahoo.com",
106
+ "yahoo.co": "yahoo.com",
107
+ "yahoo.om": "yahoo.com",
108
+ "yahoo.cm": "yahoo.com",
109
+ "yahoo.con": "yahoo.com",
110
+ "yahoo.vom": "yahoo.com",
111
+ "tahoo.com": "yahoo.com",
112
+ "uahoo.com": "yahoo.com",
113
+ "yagoo.com": "yahoo.com",
114
+ // Hotmail typos
115
+ "hotmial.com": "hotmail.com",
116
+ "hotmal.com": "hotmail.com",
117
+ "hotmai.com": "hotmail.com",
118
+ "homail.com": "hotmail.com",
119
+ "hotmil.com": "hotmail.com",
120
+ "hotamail.com": "hotmail.com",
121
+ "hotmaill.com": "hotmail.com",
122
+ "hotmail.co": "hotmail.com",
123
+ "hotmail.om": "hotmail.com",
124
+ "hotmail.cm": "hotmail.com",
125
+ "hotmail.con": "hotmail.com",
126
+ "hitmail.com": "hotmail.com",
127
+ "hormail.com": "hotmail.com",
128
+ "hptmail.com": "hotmail.com",
129
+ "hotmaiil.com": "hotmail.com",
130
+ // Outlook typos
131
+ "outlok.com": "outlook.com",
132
+ "outloo.com": "outlook.com",
133
+ "outlook.co": "outlook.com",
134
+ "outlook.om": "outlook.com",
135
+ "outlook.cm": "outlook.com",
136
+ "outlook.con": "outlook.com",
137
+ "outllook.com": "outlook.com",
138
+ "outlookk.com": "outlook.com",
139
+ "outloook.com": "outlook.com",
140
+ "putlook.com": "outlook.com",
141
+ "oitlook.com": "outlook.com",
142
+ "outlool.com": "outlook.com",
143
+ // iCloud typos
144
+ "iclould.com": "icloud.com",
145
+ "icloud.co": "icloud.com",
146
+ "icloud.om": "icloud.com",
147
+ "icoud.com": "icloud.com",
148
+ "icloude.com": "icloud.com",
149
+ "iclud.com": "icloud.com",
150
+ "iclod.com": "icloud.com",
151
+ // Protonmail typos
152
+ "protonmal.com": "protonmail.com",
153
+ "protonmial.com": "protonmail.com",
154
+ "protonmai.com": "protonmail.com",
155
+ "protonmail.co": "protonmail.com",
156
+ "protonmail.om": "protonmail.com",
157
+ "protonmail.cm": "protonmail.com",
158
+ "protnmail.com": "protonmail.com",
159
+ "protommail.com": "protonmail.com",
160
+ // AOL typos
161
+ "aol.co": "aol.com",
162
+ "aol.om": "aol.com",
163
+ "aol.cm": "aol.com",
164
+ "ao.com": "aol.com",
165
+ "aoll.com": "aol.com",
166
+ // Live typos
167
+ "live.co": "live.com",
168
+ "live.om": "live.com",
169
+ "live.cm": "live.com",
170
+ "live.con": "live.com",
171
+ "livve.com": "live.com",
172
+ // MSN typos
173
+ "msn.co": "msn.com",
174
+ "msn.om": "msn.com",
175
+ "msn.cm": "msn.com",
176
+ "mns.com": "msn.com",
177
+ // Common TLD typos
178
+ ".con": ".com",
179
+ ".vom": ".com",
180
+ ".cpm": ".com",
181
+ ".ocm": ".com",
182
+ ".co,": ".com",
183
+ ".xom": ".com",
184
+ ".cim": ".com",
185
+ ".comm": ".com",
186
+ ".ner": ".net",
187
+ ".nte": ".net",
188
+ ".nett": ".net",
189
+ ".rog": ".org",
190
+ ".ogr": ".org",
191
+ ".orgg": ".org"
192
+ };
193
+ var popularDomains = [
194
+ "gmail.com",
195
+ "yahoo.com",
196
+ "hotmail.com",
197
+ "outlook.com",
198
+ "icloud.com",
199
+ "aol.com",
200
+ "protonmail.com",
201
+ "live.com",
202
+ "msn.com",
203
+ "mail.com",
204
+ "zoho.com",
205
+ "yandex.com",
206
+ "gmx.com",
207
+ "fastmail.com"
208
+ ];
209
+ function levenshteinDistance(str1, str2) {
210
+ const m = str1.length;
211
+ const n = str2.length;
212
+ const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
213
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
214
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
215
+ for (let i = 1; i <= m; i++) {
216
+ for (let j = 1; j <= n; j++) {
217
+ if (str1[i - 1] === str2[j - 1]) {
218
+ dp[i][j] = dp[i - 1][j - 1];
219
+ } else {
220
+ dp[i][j] = 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
221
+ }
222
+ }
223
+ }
224
+ return dp[m][n];
225
+ }
226
+ function getSuggestion(domain) {
227
+ const lowerDomain = domain.toLowerCase();
228
+ if (popularDomains.includes(lowerDomain)) {
229
+ return null;
230
+ }
231
+ if (domainTypos[lowerDomain]) {
232
+ return domainTypos[lowerDomain];
233
+ }
234
+ for (const [typo, correction] of Object.entries(domainTypos)) {
235
+ if (typo.startsWith(".") && lowerDomain.endsWith(typo)) {
236
+ return lowerDomain.slice(0, -typo.length) + correction;
237
+ }
238
+ }
239
+ let bestMatch = null;
240
+ let bestDistance = Infinity;
241
+ for (const popular of popularDomains) {
242
+ const distance = levenshteinDistance(lowerDomain, popular);
243
+ if (distance > 0 && distance <= 2 && distance < bestDistance) {
244
+ bestDistance = distance;
245
+ bestMatch = popular;
246
+ }
247
+ }
248
+ return bestMatch;
249
+ }
250
+ function getEmailSuggestion(email) {
251
+ const atIndex = email.indexOf("@");
252
+ if (atIndex === -1) return null;
253
+ const username = email.slice(0, atIndex);
254
+ const domain = email.slice(atIndex + 1);
255
+ const suggestedDomain = getSuggestion(domain);
256
+ if (suggestedDomain && suggestedDomain !== domain.toLowerCase()) {
257
+ return `${username}@${suggestedDomain}`;
258
+ }
259
+ return null;
260
+ }
261
+
262
+ // src/validators/syntax.ts
263
+ var EMAIL_REGEX = /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$/i;
264
+ var SIMPLE_EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
265
+ var MAX_EMAIL_LENGTH = 254;
266
+ var MAX_LOCAL_LENGTH = 64;
267
+ var MAX_DOMAIN_LENGTH = 253;
268
+ function validateSyntax(email) {
269
+ const trimmedEmail = email.trim();
270
+ const normalizedEmail = normalizeEmail(trimmedEmail);
271
+ const invalidResult = {
272
+ address: trimmedEmail,
273
+ domain: "",
274
+ username: "",
275
+ isValidSyntax: false,
276
+ normalizedEmail,
277
+ suggestion: getEmailSuggestion(normalizedEmail)
278
+ };
279
+ if (!trimmedEmail) {
280
+ return invalidResult;
281
+ }
282
+ if (trimmedEmail.length > MAX_EMAIL_LENGTH) {
283
+ return invalidResult;
284
+ }
285
+ const parts = extractParts(trimmedEmail);
286
+ if (!parts) {
287
+ return invalidResult;
288
+ }
289
+ const { username, domain } = parts;
290
+ if (username.length > MAX_LOCAL_LENGTH) {
291
+ return {
292
+ ...invalidResult,
293
+ domain,
294
+ username
295
+ };
296
+ }
297
+ if (domain.length > MAX_DOMAIN_LENGTH) {
298
+ return {
299
+ ...invalidResult,
300
+ domain,
301
+ username
302
+ };
303
+ }
304
+ if (!username || !domain) {
305
+ return {
306
+ ...invalidResult,
307
+ domain,
308
+ username
309
+ };
310
+ }
311
+ if (!domain.includes(".") && !domain.startsWith("[")) {
312
+ return {
313
+ ...invalidResult,
314
+ domain,
315
+ username
316
+ };
317
+ }
318
+ const labels = domain.split(".");
319
+ for (const label of labels) {
320
+ if (label.length === 0 || label.length > 63) {
321
+ return {
322
+ ...invalidResult,
323
+ domain,
324
+ username
325
+ };
326
+ }
327
+ if (label.startsWith("-") || label.endsWith("-")) {
328
+ return {
329
+ ...invalidResult,
330
+ domain,
331
+ username
332
+ };
333
+ }
334
+ if (!/^[a-z0-9-]+$/i.test(label)) {
335
+ return {
336
+ ...invalidResult,
337
+ domain,
338
+ username
339
+ };
340
+ }
341
+ }
342
+ const tld = labels[labels.length - 1];
343
+ if (tld.length < 2 || !/^[a-z]+$/i.test(tld)) {
344
+ return {
345
+ ...invalidResult,
346
+ domain,
347
+ username
348
+ };
349
+ }
350
+ if (!SIMPLE_EMAIL_REGEX.test(trimmedEmail)) {
351
+ return {
352
+ ...invalidResult,
353
+ domain,
354
+ username
355
+ };
356
+ }
357
+ const isValid = EMAIL_REGEX.test(trimmedEmail);
358
+ return {
359
+ address: trimmedEmail,
360
+ domain: domain.toLowerCase(),
361
+ username,
362
+ isValidSyntax: isValid,
363
+ normalizedEmail,
364
+ suggestion: isValid ? getEmailSuggestion(normalizedEmail) : invalidResult.suggestion
365
+ };
366
+ }
367
+
368
+ // src/validators/dns.ts
369
+ var import_dns = __toESM(require("dns"));
370
+ var import_util = require("util");
371
+
372
+ // src/utils/cache.ts
373
+ var DnsCache = class {
374
+ cache = /* @__PURE__ */ new Map();
375
+ ttl;
376
+ /**
377
+ * Create a new DNS cache
378
+ * @param ttl Time-to-live in milliseconds (default: 5 minutes)
379
+ */
380
+ constructor(ttl = 3e5) {
381
+ this.ttl = ttl;
382
+ }
383
+ /**
384
+ * Get a cached entry for a domain
385
+ * @param domain The domain to look up
386
+ * @returns The cached entry or null if not found/expired
387
+ */
388
+ get(domain) {
389
+ const key = domain.toLowerCase();
390
+ const entry = this.cache.get(key);
391
+ if (!entry) {
392
+ return null;
393
+ }
394
+ if (Date.now() - entry.timestamp > this.ttl) {
395
+ this.cache.delete(key);
396
+ return null;
397
+ }
398
+ return entry;
399
+ }
400
+ /**
401
+ * Set a cache entry for a domain
402
+ * @param domain The domain to cache
403
+ * @param records The MX records
404
+ * @param acceptsMail Whether the domain accepts mail
405
+ */
406
+ set(domain, records, acceptsMail) {
407
+ const key = domain.toLowerCase();
408
+ this.cache.set(key, {
409
+ records,
410
+ acceptsMail,
411
+ timestamp: Date.now()
412
+ });
413
+ }
414
+ /**
415
+ * Clear all cached entries
416
+ */
417
+ clear() {
418
+ this.cache.clear();
419
+ }
420
+ /**
421
+ * Remove expired entries from the cache
422
+ */
423
+ cleanup() {
424
+ const now = Date.now();
425
+ for (const [key, entry] of this.cache.entries()) {
426
+ if (now - entry.timestamp > this.ttl) {
427
+ this.cache.delete(key);
428
+ }
429
+ }
430
+ }
431
+ /**
432
+ * Get the number of cached entries
433
+ */
434
+ get size() {
435
+ return this.cache.size;
436
+ }
437
+ /**
438
+ * Set the TTL for the cache
439
+ * @param ttl Time-to-live in milliseconds
440
+ */
441
+ setTtl(ttl) {
442
+ this.ttl = ttl;
443
+ }
444
+ };
445
+ var globalDnsCache = new DnsCache();
446
+
447
+ // src/validators/dns.ts
448
+ var resolveMx = (0, import_util.promisify)(import_dns.default.resolveMx);
449
+ var resolve4 = (0, import_util.promisify)(import_dns.default.resolve4);
450
+ var resolve6 = (0, import_util.promisify)(import_dns.default.resolve6);
451
+ function sortMxRecords(records) {
452
+ return records.sort((a, b) => a.priority - b.priority).map((r) => cleanDomain(r.exchange));
453
+ }
454
+ async function verifyMx(domain, options = {}, cache) {
455
+ const cleanedDomain = cleanDomain(domain);
456
+ const dnsCache = cache || globalDnsCache;
457
+ if (options.useDnsCache !== false) {
458
+ const cached = dnsCache.get(cleanedDomain);
459
+ if (cached) {
460
+ return {
461
+ acceptsMail: cached.acceptsMail,
462
+ records: cached.records
463
+ };
464
+ }
465
+ }
466
+ try {
467
+ const mxRecords = await resolveMx(cleanedDomain);
468
+ if (!mxRecords || mxRecords.length === 0) {
469
+ return await fallbackToAddressRecords(cleanedDomain, dnsCache, options);
470
+ }
471
+ if (mxRecords.length === 1 && mxRecords[0].priority === 0 && (mxRecords[0].exchange === "" || mxRecords[0].exchange === "." || mxRecords[0].exchange === "0.0.0.0")) {
472
+ const result2 = {
473
+ acceptsMail: false,
474
+ records: []
475
+ };
476
+ if (options.useDnsCache !== false) {
477
+ dnsCache.set(cleanedDomain, [], false);
478
+ }
479
+ return result2;
480
+ }
481
+ const sortedRecords = sortMxRecords(mxRecords);
482
+ const validRecords = sortedRecords.filter(
483
+ (r) => r && r !== "" && r !== "." && r !== "0.0.0.0" && r !== "localhost"
484
+ );
485
+ if (validRecords.length === 0) {
486
+ return await fallbackToAddressRecords(cleanedDomain, dnsCache, options);
487
+ }
488
+ const result = {
489
+ acceptsMail: true,
490
+ records: validRecords
491
+ };
492
+ if (options.useDnsCache !== false) {
493
+ dnsCache.set(cleanedDomain, validRecords, true);
494
+ }
495
+ return result;
496
+ } catch (error) {
497
+ const err = error;
498
+ if (err.code === "ENODATA" || err.code === "ENOTFOUND") {
499
+ return await fallbackToAddressRecords(cleanedDomain, dnsCache, options);
500
+ }
501
+ if (err.code === "ENOTFOUND" || err.code === "SERVFAIL") {
502
+ const result = {
503
+ acceptsMail: false,
504
+ records: []
505
+ };
506
+ if (options.useDnsCache !== false) {
507
+ dnsCache.set(cleanedDomain, [], false);
508
+ }
509
+ return result;
510
+ }
511
+ return {
512
+ acceptsMail: false,
513
+ records: []
514
+ };
515
+ }
516
+ }
517
+ async function fallbackToAddressRecords(domain, cache, options) {
518
+ try {
519
+ const aRecords = await resolve4(domain);
520
+ if (aRecords && aRecords.length > 0) {
521
+ const result2 = {
522
+ acceptsMail: true,
523
+ records: [domain]
524
+ // Use the domain itself as the mail host
525
+ };
526
+ if (options.useDnsCache !== false) {
527
+ cache.set(domain, [domain], true);
528
+ }
529
+ return result2;
530
+ }
531
+ } catch {
532
+ }
533
+ try {
534
+ const aaaaRecords = await resolve6(domain);
535
+ if (aaaaRecords && aaaaRecords.length > 0) {
536
+ const result2 = {
537
+ acceptsMail: true,
538
+ records: [domain]
539
+ // Use the domain itself as the mail host
540
+ };
541
+ if (options.useDnsCache !== false) {
542
+ cache.set(domain, [domain], true);
543
+ }
544
+ return result2;
545
+ }
546
+ } catch {
547
+ }
548
+ const result = {
549
+ acceptsMail: false,
550
+ records: []
551
+ };
552
+ if (options.useDnsCache !== false) {
553
+ cache.set(domain, [], false);
554
+ }
555
+ return result;
556
+ }
557
+
558
+ // src/validators/smtp.ts
559
+ var import_net = __toESM(require("net"));
560
+ var import_os = __toESM(require("os"));
561
+ var SMTP_PORT = 25;
562
+ var DEFAULT_TIMEOUT = 1e4;
563
+ var SmtpErrorType = {
564
+ CONNECTION_FAILED: "ConnectionFailed",
565
+ TIMEOUT: "Timeout",
566
+ INVALID_RESPONSE: "InvalidResponse",
567
+ HELO_FAILED: "HeloFailed",
568
+ MAIL_FROM_FAILED: "MailFromFailed",
569
+ RCPT_TO_FAILED: "RcptToFailed",
570
+ MAILBOX_NOT_FOUND: "MailboxNotFound",
571
+ MAILBOX_DISABLED: "MailboxDisabled",
572
+ GREYLISTED: "Greylisted",
573
+ BLOCKED: "Blocked",
574
+ UNKNOWN: "Unknown"
575
+ };
576
+ function parseResponse(data) {
577
+ const lines = data.trim().split("\r\n");
578
+ const lastLine = lines[lines.length - 1];
579
+ const code = parseInt(lastLine.substring(0, 3), 10);
580
+ const message = lastLine.substring(4);
581
+ return { code, message };
582
+ }
583
+ function isSuccessResponse(code) {
584
+ return code >= 200 && code < 400;
585
+ }
586
+ function sendCommand(socket, command, timeout) {
587
+ return new Promise((resolve, reject) => {
588
+ const timeoutId = setTimeout(() => {
589
+ reject(new Error("Command timeout"));
590
+ }, timeout);
591
+ const onData = (data) => {
592
+ clearTimeout(timeoutId);
593
+ socket.removeListener("data", onData);
594
+ socket.removeListener("error", onError);
595
+ const response = parseResponse(data.toString());
596
+ resolve(response);
597
+ };
598
+ const onError = (err) => {
599
+ clearTimeout(timeoutId);
600
+ socket.removeListener("data", onData);
601
+ reject(err);
602
+ };
603
+ socket.once("data", onData);
604
+ socket.once("error", onError);
605
+ if (command) {
606
+ socket.write(command + "\r\n");
607
+ }
608
+ });
609
+ }
610
+ function waitForGreeting(socket, timeout) {
611
+ return new Promise((resolve, reject) => {
612
+ const timeoutId = setTimeout(() => {
613
+ reject(new Error("Greeting timeout"));
614
+ }, timeout);
615
+ const onData = (data) => {
616
+ clearTimeout(timeoutId);
617
+ socket.removeListener("data", onData);
618
+ socket.removeListener("error", onError);
619
+ const response = parseResponse(data.toString());
620
+ resolve(response);
621
+ };
622
+ const onError = (err) => {
623
+ clearTimeout(timeoutId);
624
+ socket.removeListener("data", onData);
625
+ reject(err);
626
+ };
627
+ socket.once("data", onData);
628
+ socket.once("error", onError);
629
+ });
630
+ }
631
+ function generateRandomEmail(domain) {
632
+ const random = Math.random().toString(36).substring(2, 15);
633
+ return `${random}@${domain}`;
634
+ }
635
+ async function verifySmtp(email, mxHost, options = {}) {
636
+ const timeout = options.smtpTimeout || DEFAULT_TIMEOUT;
637
+ const heloHost = options.heloHost || import_os.default.hostname() || "localhost";
638
+ const senderAddress = options.senderAddress || "";
639
+ const detectCatchAll = options.detectCatchAll !== false;
640
+ const domain = email.split("@")[1];
641
+ return new Promise((resolve) => {
642
+ const socket = new import_net.default.Socket();
643
+ let resolved = false;
644
+ const cleanup = () => {
645
+ if (!socket.destroyed) {
646
+ try {
647
+ socket.write("QUIT\r\n");
648
+ } catch {
649
+ }
650
+ socket.destroy();
651
+ }
652
+ };
653
+ const finish = (result) => {
654
+ if (!resolved) {
655
+ resolved = true;
656
+ cleanup();
657
+ resolve(result);
658
+ }
659
+ };
660
+ socket.setTimeout(timeout);
661
+ socket.on("timeout", () => {
662
+ finish({
663
+ canConnectSmtp: false,
664
+ isDeliverable: false,
665
+ isCatchAll: false,
666
+ error: {
667
+ type: SmtpErrorType.TIMEOUT,
668
+ message: "Connection timed out"
669
+ }
670
+ });
671
+ });
672
+ socket.on("error", (err) => {
673
+ finish({
674
+ canConnectSmtp: false,
675
+ isDeliverable: false,
676
+ isCatchAll: false,
677
+ error: {
678
+ type: SmtpErrorType.CONNECTION_FAILED,
679
+ message: err.message
680
+ }
681
+ });
682
+ });
683
+ socket.connect(SMTP_PORT, mxHost, async () => {
684
+ try {
685
+ const greeting = await waitForGreeting(socket, timeout);
686
+ if (!isSuccessResponse(greeting.code)) {
687
+ finish({
688
+ canConnectSmtp: false,
689
+ isDeliverable: false,
690
+ isCatchAll: false,
691
+ error: {
692
+ type: SmtpErrorType.BLOCKED,
693
+ message: `Server rejected connection: ${greeting.message}`
694
+ }
695
+ });
696
+ return;
697
+ }
698
+ let heloResponse = await sendCommand(socket, `EHLO ${heloHost}`, timeout);
699
+ if (!isSuccessResponse(heloResponse.code)) {
700
+ heloResponse = await sendCommand(socket, `HELO ${heloHost}`, timeout);
701
+ if (!isSuccessResponse(heloResponse.code)) {
702
+ finish({
703
+ canConnectSmtp: true,
704
+ isDeliverable: false,
705
+ isCatchAll: false,
706
+ error: {
707
+ type: SmtpErrorType.HELO_FAILED,
708
+ message: `HELO failed: ${heloResponse.message}`
709
+ }
710
+ });
711
+ return;
712
+ }
713
+ }
714
+ const mailFromResponse = await sendCommand(
715
+ socket,
716
+ `MAIL FROM:<${senderAddress}>`,
717
+ timeout
718
+ );
719
+ if (!isSuccessResponse(mailFromResponse.code)) {
720
+ finish({
721
+ canConnectSmtp: true,
722
+ isDeliverable: false,
723
+ isCatchAll: false,
724
+ error: {
725
+ type: SmtpErrorType.MAIL_FROM_FAILED,
726
+ message: `MAIL FROM failed: ${mailFromResponse.message}`
727
+ }
728
+ });
729
+ return;
730
+ }
731
+ const rcptResponse = await sendCommand(socket, `RCPT TO:<${email}>`, timeout);
732
+ let isCatchAll = false;
733
+ if (detectCatchAll && isSuccessResponse(rcptResponse.code)) {
734
+ const randomEmail = generateRandomEmail(domain);
735
+ const catchAllResponse = await sendCommand(
736
+ socket,
737
+ `RCPT TO:<${randomEmail}>`,
738
+ timeout
739
+ );
740
+ isCatchAll = isSuccessResponse(catchAllResponse.code);
741
+ }
742
+ if (isSuccessResponse(rcptResponse.code)) {
743
+ finish({
744
+ canConnectSmtp: true,
745
+ isDeliverable: true,
746
+ isCatchAll,
747
+ error: null
748
+ });
749
+ } else if (rcptResponse.code === 550) {
750
+ finish({
751
+ canConnectSmtp: true,
752
+ isDeliverable: false,
753
+ isCatchAll: false,
754
+ error: {
755
+ type: SmtpErrorType.MAILBOX_NOT_FOUND,
756
+ message: rcptResponse.message
757
+ }
758
+ });
759
+ } else if (rcptResponse.code === 551 || rcptResponse.code === 553) {
760
+ finish({
761
+ canConnectSmtp: true,
762
+ isDeliverable: false,
763
+ isCatchAll: false,
764
+ error: {
765
+ type: SmtpErrorType.MAILBOX_NOT_FOUND,
766
+ message: rcptResponse.message
767
+ }
768
+ });
769
+ } else if (rcptResponse.code === 552) {
770
+ finish({
771
+ canConnectSmtp: true,
772
+ isDeliverable: false,
773
+ isCatchAll: false,
774
+ error: {
775
+ type: SmtpErrorType.MAILBOX_DISABLED,
776
+ message: rcptResponse.message
777
+ }
778
+ });
779
+ } else if (rcptResponse.code === 450 || rcptResponse.code === 451) {
780
+ finish({
781
+ canConnectSmtp: true,
782
+ isDeliverable: false,
783
+ isCatchAll: false,
784
+ error: {
785
+ type: SmtpErrorType.GREYLISTED,
786
+ message: rcptResponse.message
787
+ }
788
+ });
789
+ } else if (rcptResponse.code >= 500) {
790
+ finish({
791
+ canConnectSmtp: true,
792
+ isDeliverable: false,
793
+ isCatchAll: false,
794
+ error: {
795
+ type: SmtpErrorType.RCPT_TO_FAILED,
796
+ message: rcptResponse.message
797
+ }
798
+ });
799
+ } else {
800
+ finish({
801
+ canConnectSmtp: true,
802
+ isDeliverable: false,
803
+ isCatchAll: false,
804
+ error: {
805
+ type: SmtpErrorType.UNKNOWN,
806
+ message: `Unexpected response code ${rcptResponse.code}: ${rcptResponse.message}`
807
+ }
808
+ });
809
+ }
810
+ } catch (err) {
811
+ const error = err;
812
+ finish({
813
+ canConnectSmtp: true,
814
+ isDeliverable: false,
815
+ isCatchAll: false,
816
+ error: {
817
+ type: SmtpErrorType.UNKNOWN,
818
+ message: error.message
819
+ }
820
+ });
821
+ }
822
+ });
823
+ });
824
+ }
825
+ async function verifySmtpWithFallback(email, mxHosts, options = {}) {
826
+ if (mxHosts.length === 0) {
827
+ return {
828
+ canConnectSmtp: false,
829
+ isDeliverable: false,
830
+ isCatchAll: false,
831
+ error: {
832
+ type: SmtpErrorType.CONNECTION_FAILED,
833
+ message: "No MX hosts available"
834
+ }
835
+ };
836
+ }
837
+ let lastResult = null;
838
+ for (const mxHost of mxHosts) {
839
+ const result = await verifySmtp(email, mxHost, options);
840
+ if (result.isDeliverable || result.error?.type === SmtpErrorType.MAILBOX_NOT_FOUND) {
841
+ return result;
842
+ }
843
+ if (result.canConnectSmtp) {
844
+ lastResult = result;
845
+ }
846
+ if (!result.canConnectSmtp) {
847
+ lastResult = result;
848
+ continue;
849
+ }
850
+ }
851
+ return lastResult || {
852
+ canConnectSmtp: false,
853
+ isDeliverable: false,
854
+ isCatchAll: false,
855
+ error: {
856
+ type: SmtpErrorType.CONNECTION_FAILED,
857
+ message: "Failed to connect to any MX host"
858
+ }
859
+ };
860
+ }
861
+
862
+ // src/validators/misc.ts
863
+ var import_crypto = __toESM(require("crypto"));
864
+ var import_https = __toESM(require("https"));
865
+
866
+ // src/data/disposable.ts
867
+ var disposableDomains = /* @__PURE__ */ new Set([
868
+ // Popular disposable email services
869
+ "10minutemail.com",
870
+ "10minutemail.net",
871
+ "20minutemail.com",
872
+ "33mail.com",
873
+ "guerrillamail.com",
874
+ "guerrillamail.org",
875
+ "guerrillamail.net",
876
+ "guerrillamail.biz",
877
+ "guerrillamail.de",
878
+ "guerrillamailblock.com",
879
+ "sharklasers.com",
880
+ "grr.la",
881
+ "pokemail.net",
882
+ "spam4.me",
883
+ "mailinator.com",
884
+ "mailinator.net",
885
+ "mailinator.org",
886
+ "mailinator2.com",
887
+ "mailinater.com",
888
+ "tempmail.com",
889
+ "tempmail.net",
890
+ "temp-mail.org",
891
+ "temp-mail.io",
892
+ "tempail.com",
893
+ "tempmailaddress.com",
894
+ "throwaway.email",
895
+ "throwawaymail.com",
896
+ "trashmail.com",
897
+ "trashmail.net",
898
+ "trashmail.org",
899
+ "trashmail.me",
900
+ "trashemail.de",
901
+ "fakeinbox.com",
902
+ "fakemailgenerator.com",
903
+ "getnada.com",
904
+ "nada.email",
905
+ "getairmail.com",
906
+ "airmail.cc",
907
+ "dispostable.com",
908
+ "disposableemailaddresses.com",
909
+ "emailondeck.com",
910
+ "yopmail.com",
911
+ "yopmail.fr",
912
+ "yopmail.net",
913
+ "cool.fr.nf",
914
+ "jetable.fr.nf",
915
+ "nospam.ze.tc",
916
+ "nomail.xl.cx",
917
+ "mega.zik.dj",
918
+ "speed.1s.fr",
919
+ "courriel.fr.nf",
920
+ "moncourrier.fr.nf",
921
+ "monemail.fr.nf",
922
+ "monmail.fr.nf",
923
+ "mohmal.com",
924
+ "mailnesia.com",
925
+ "maildrop.cc",
926
+ "mailsac.com",
927
+ "mintemail.com",
928
+ "mytemp.email",
929
+ "mytrashmail.com",
930
+ "nowmymail.com",
931
+ "spambox.us",
932
+ "spamfree24.org",
933
+ "spamgourmet.com",
934
+ "spamspot.com",
935
+ "tempinbox.com",
936
+ "tempomail.fr",
937
+ "temporaryemail.net",
938
+ "temporaryforwarding.com",
939
+ "temporaryinbox.com",
940
+ "thankyou2010.com",
941
+ "thisisnotmyrealemail.com",
942
+ "throam.com",
943
+ "tmail.ws",
944
+ "tmpmail.net",
945
+ "tmpmail.org",
946
+ "wegwerfmail.de",
947
+ "wegwerfmail.net",
948
+ "wegwerfmail.org",
949
+ "wh4f.org",
950
+ "willhackforfood.biz",
951
+ "willselfdestruct.com",
952
+ "emailfake.com",
953
+ "emkei.cz",
954
+ "anonymbox.com",
955
+ "discard.email",
956
+ "discardmail.com",
957
+ "discardmail.de",
958
+ "spambog.com",
959
+ "spambog.de",
960
+ "spambog.ru",
961
+ "mailcatch.com",
962
+ "mail-temp.com",
963
+ "mailtemp.info",
964
+ "mailzilla.com",
965
+ "mailzilla.org",
966
+ "binkmail.com",
967
+ "bobmail.info",
968
+ "burnthespam.info",
969
+ "buyusedlibrarybooks.org",
970
+ "byom.de",
971
+ "deadaddress.com",
972
+ "despam.it",
973
+ "devnullmail.com",
974
+ "dfgh.net",
975
+ "e4ward.com",
976
+ "emailias.com",
977
+ "emailigo.de",
978
+ "emailsensei.com",
979
+ "emailtemporario.com.br",
980
+ "emailwarden.com",
981
+ "explodemail.com",
982
+ "fastacura.com",
983
+ "filzmail.com",
984
+ "fizmail.com",
985
+ "frapmail.com",
986
+ "gishpuppy.com",
987
+ "great-host.in",
988
+ "greensloth.com",
989
+ "haltospam.com",
990
+ "hidzz.com",
991
+ "hmamail.com",
992
+ "imails.info",
993
+ "incognitomail.com",
994
+ "incognitomail.net",
995
+ "incognitomail.org",
996
+ "infocom.zp.ua",
997
+ "instantemailaddress.com",
998
+ "ipoo.org",
999
+ "irish2me.com",
1000
+ "jetable.com",
1001
+ "kasmail.com",
1002
+ "klassmaster.com",
1003
+ "klzlv.com",
1004
+ "koszmail.pl",
1005
+ "kulturbetrieb.info",
1006
+ "kurzepost.de",
1007
+ "lawlita.com",
1008
+ "letthemeatspam.com",
1009
+ "lhsdv.com",
1010
+ "lifebyfood.com",
1011
+ "link2mail.net",
1012
+ "litedrop.com",
1013
+ "lol.ovpn.to",
1014
+ "lookugly.com",
1015
+ "lortemail.dk",
1016
+ "lovemeleaveme.com",
1017
+ "lr78.com",
1018
+ "maboard.com",
1019
+ "mail2rss.org",
1020
+ "mail333.com",
1021
+ "mailbidon.com",
1022
+ "mailblocks.com",
1023
+ "maildu.de",
1024
+ "maileater.com",
1025
+ "mailexpire.com",
1026
+ "mailfa.tk",
1027
+ "mailfork.com",
1028
+ "mailfreeonline.com",
1029
+ "mailguard.me",
1030
+ "mailimate.com",
1031
+ "mailin8r.com",
1032
+ "mailinblack.com",
1033
+ "mailincubator.com",
1034
+ "mailme.ir",
1035
+ "mailme.lv",
1036
+ "mailmetrash.com",
1037
+ "mailmoat.com",
1038
+ "mailnull.com",
1039
+ "mailorg.org",
1040
+ "mailscrap.com",
1041
+ "mailshell.com",
1042
+ "mailsiphon.com",
1043
+ "mailslite.com",
1044
+ "mailtemp.net",
1045
+ "mailtothis.com",
1046
+ "mailzi.ru",
1047
+ "makemetheking.com",
1048
+ "mbx.cc",
1049
+ "mega.zik.dj",
1050
+ "meltmail.com",
1051
+ "messagebeamer.de",
1052
+ "mezimages.net",
1053
+ "mierdamail.com",
1054
+ "migmail.pl",
1055
+ "migumail.com",
1056
+ "mintemail.com",
1057
+ "moburl.com",
1058
+ "moncourrier.fr.nf",
1059
+ "monemail.fr.nf",
1060
+ "monmail.fr.nf",
1061
+ "monumentmail.com",
1062
+ "ms51.hinet.net",
1063
+ "msb.minsmail.com",
1064
+ "mt2009.com",
1065
+ "mx0.wwwnew.eu",
1066
+ "mycleaninbox.net",
1067
+ "mypartyclip.de",
1068
+ "myphantomemail.com",
1069
+ "mysamp.de",
1070
+ "myspaceinc.com",
1071
+ "myspaceinc.net",
1072
+ "myspacepimpedup.com",
1073
+ "mytempemail.com",
1074
+ "neomailbox.com",
1075
+ "nepwk.com",
1076
+ "nervmich.net",
1077
+ "nervtmansen.de",
1078
+ "netmails.com",
1079
+ "netmails.net",
1080
+ "netzidiot.de",
1081
+ "neverbox.com",
1082
+ "no-spam.ws",
1083
+ "nobulk.com",
1084
+ "noclickemail.com",
1085
+ "nogmailspam.info",
1086
+ "nomail.xl.cx",
1087
+ "nomail2me.com",
1088
+ "nomorespamemails.com",
1089
+ "nospam.ze.tc",
1090
+ "nospam4.us",
1091
+ "nospamfor.us",
1092
+ "nospammail.net",
1093
+ "notmailinator.com",
1094
+ "nowhere.org",
1095
+ "nowmymail.com",
1096
+ "nurfuerspam.de",
1097
+ "nus.edu.sg",
1098
+ "objectmail.com",
1099
+ "obobbo.com",
1100
+ "oneoffemail.com",
1101
+ "onewaymail.com",
1102
+ "onlatedotcom.info",
1103
+ "online.ms",
1104
+ "oopi.org",
1105
+ "opayq.com",
1106
+ "ordinaryamerican.net",
1107
+ "otherinbox.com",
1108
+ "ourklips.com",
1109
+ "outlawspam.com",
1110
+ "ovpn.to",
1111
+ "owlpic.com",
1112
+ "pancakemail.com",
1113
+ "pjjkp.com",
1114
+ "plexolan.de",
1115
+ "poczta.onet.pl",
1116
+ "politikerclub.de",
1117
+ "poofy.org",
1118
+ "pookmail.com",
1119
+ "privacy.net",
1120
+ "privy-mail.com",
1121
+ "privymail.de",
1122
+ "proxymail.eu",
1123
+ "prtnx.com",
1124
+ "punkass.com",
1125
+ "putthisinyourspamdatabase.com",
1126
+ "qq.com",
1127
+ "quickinbox.com",
1128
+ "quickmail.nl",
1129
+ "rcpt.at",
1130
+ "reallymymail.com",
1131
+ "realtyalerts.ca",
1132
+ "recode.me",
1133
+ "recursor.net",
1134
+ "recyclemail.dk",
1135
+ "regbypass.com",
1136
+ "regbypass.comsafe-mail.net",
1137
+ "rejectmail.com",
1138
+ "remail.cf",
1139
+ "rhyta.com",
1140
+ "rklips.com",
1141
+ "rmqkr.net",
1142
+ "rppkn.com",
1143
+ "rtrtr.com",
1144
+ "s0ny.net",
1145
+ "safe-mail.net",
1146
+ "safersignup.de",
1147
+ "safetymail.info",
1148
+ "safetypost.de",
1149
+ "sandelf.de",
1150
+ "saynotospams.com",
1151
+ "schafmail.de",
1152
+ "selfdestructingmail.com",
1153
+ "sendspamhere.com",
1154
+ "shieldemail.com",
1155
+ "shiftmail.com",
1156
+ "shitmail.me",
1157
+ "shortmail.net",
1158
+ "shut.name",
1159
+ "shut.ws",
1160
+ "sibmail.com",
1161
+ "sinnlos-mail.de",
1162
+ "siteposter.net",
1163
+ "skeefmail.com",
1164
+ "slaskpost.se",
1165
+ "slopsbox.com",
1166
+ "slowfoodfoothills.xyz",
1167
+ "smashmail.de",
1168
+ "smellfear.com",
1169
+ "snakemail.com",
1170
+ "sneakemail.com",
1171
+ "sneakmail.de",
1172
+ "snkmail.com",
1173
+ "sofimail.com",
1174
+ "sofort-mail.de",
1175
+ "softpls.asia",
1176
+ "sogetthis.com",
1177
+ "sohu.com",
1178
+ "soisz.com",
1179
+ "solvemail.info",
1180
+ "soodonims.com",
1181
+ "spam.la",
1182
+ "spam.su",
1183
+ "spamavert.com",
1184
+ "spambob.com",
1185
+ "spambob.net",
1186
+ "spambob.org",
1187
+ "spambog.com",
1188
+ "spambog.de",
1189
+ "spambog.ru",
1190
+ "spambox.info",
1191
+ "spambox.irishspringrealty.com",
1192
+ "spambox.us",
1193
+ "spamcannon.com",
1194
+ "spamcannon.net",
1195
+ "spamcero.com",
1196
+ "spamcon.org",
1197
+ "spamcorptastic.com",
1198
+ "spamcowboy.com",
1199
+ "spamcowboy.net",
1200
+ "spamcowboy.org",
1201
+ "spamday.com",
1202
+ "spamex.com",
1203
+ "spamfree.eu",
1204
+ "spamfree24.com",
1205
+ "spamfree24.de",
1206
+ "spamfree24.eu",
1207
+ "spamfree24.info",
1208
+ "spamfree24.net",
1209
+ "spamfree24.org",
1210
+ "spamgoes.in",
1211
+ "spamherelots.com",
1212
+ "spamhereplease.com",
1213
+ "spamhole.com",
1214
+ "spamify.com",
1215
+ "spaminator.de",
1216
+ "spamkill.info",
1217
+ "spaml.com",
1218
+ "spaml.de",
1219
+ "spammotel.com",
1220
+ "spamobox.com",
1221
+ "spamoff.de",
1222
+ "spamsalad.in",
1223
+ "spamslicer.com",
1224
+ "spamspot.com",
1225
+ "spamstack.net",
1226
+ "spamthis.co.uk",
1227
+ "spamtroll.net",
1228
+ "speed.1s.fr",
1229
+ "spoofmail.de",
1230
+ "squizzy.de",
1231
+ "ssoia.com",
1232
+ "startkeys.com",
1233
+ "stinkefinger.net",
1234
+ "stop-my-spam.cf",
1235
+ "stop-my-spam.com",
1236
+ "stop-my-spam.ga",
1237
+ "stop-my-spam.ml",
1238
+ "stop-my-spam.tk",
1239
+ "streetwisemail.com",
1240
+ "stuffmail.de",
1241
+ "supergreatmail.com",
1242
+ "supermailer.jp",
1243
+ "superrito.com",
1244
+ "superstachel.de",
1245
+ "suremail.info",
1246
+ "svk.jp",
1247
+ "sweetxxx.de",
1248
+ "tagyourself.com",
1249
+ "talkinator.com",
1250
+ "tapchicuoihoi.com",
1251
+ "techemail.com",
1252
+ "techgroup.me",
1253
+ "teewars.org",
1254
+ "teleosaurs.xyz",
1255
+ "teleworm.com",
1256
+ "teleworm.us",
1257
+ "temp.emeraldwebmail.com",
1258
+ "tempail.com",
1259
+ "tempalias.com",
1260
+ "tempe-mail.com",
1261
+ "tempemail.biz",
1262
+ "tempemail.co.za",
1263
+ "tempemail.com",
1264
+ "tempemail.net",
1265
+ "tempinbox.co.uk",
1266
+ "tempinbox.com",
1267
+ "tempmail.co",
1268
+ "tempmail.de",
1269
+ "tempmail.eu",
1270
+ "tempmail.it",
1271
+ "tempmail.net",
1272
+ "tempmail.us",
1273
+ "tempmail2.com",
1274
+ "tempmaildemo.com",
1275
+ "tempmailer.com",
1276
+ "tempmailer.de",
1277
+ "tempomail.fr",
1278
+ "temporarioemail.com.br",
1279
+ "temporaryemail.net",
1280
+ "temporaryemail.us",
1281
+ "temporaryforwarding.com",
1282
+ "temporaryinbox.com",
1283
+ "temporarymailaddress.com",
1284
+ "tempthe.net",
1285
+ "thankspam.info",
1286
+ "thankyou2010.com",
1287
+ "thecloudindex.com",
1288
+ "thelimestones.com",
1289
+ "thisisnotmyrealemail.com",
1290
+ "throam.com",
1291
+ "throwam.com",
1292
+ "throwawayemailaddress.com",
1293
+ "throwawaymail.com",
1294
+ "tilien.com",
1295
+ "tittbit.in",
1296
+ "tmailinator.com",
1297
+ "tmail.ws",
1298
+ "toiea.com",
1299
+ "tokenmail.de",
1300
+ "toomail.biz",
1301
+ "topranklist.de",
1302
+ "tradermail.info",
1303
+ "trash-amil.com",
1304
+ "trash-mail.at",
1305
+ "trash-mail.com",
1306
+ "trash-mail.de",
1307
+ "trash2009.com",
1308
+ "trash2010.com",
1309
+ "trash2011.com",
1310
+ "trashcanmail.com",
1311
+ "trashdevil.com",
1312
+ "trashdevil.de",
1313
+ "trashemail.de",
1314
+ "trashmail.at",
1315
+ "trashmail.com",
1316
+ "trashmail.de",
1317
+ "trashmail.me",
1318
+ "trashmail.net",
1319
+ "trashmail.org",
1320
+ "trashmail.ws",
1321
+ "trashmailer.com",
1322
+ "trashymail.com",
1323
+ "trashymail.net",
1324
+ "trbvm.com",
1325
+ "trickmail.net",
1326
+ "trillianpro.com",
1327
+ "tryalert.com",
1328
+ "turual.com",
1329
+ "twinmail.de",
1330
+ "tyldd.com",
1331
+ "uggsrock.com",
1332
+ "umail.net",
1333
+ "upliftnow.com",
1334
+ "uplipht.com",
1335
+ "uroid.com",
1336
+ "us.af",
1337
+ "valemail.net",
1338
+ "venompen.com",
1339
+ "veryrealemail.com",
1340
+ "viditag.com",
1341
+ "viralplays.com",
1342
+ "vpn.st",
1343
+ "vsimcard.com",
1344
+ "vubby.com",
1345
+ "wasteland.rfc822.org",
1346
+ "webemail.me",
1347
+ "webm4il.info",
1348
+ "webuser.in",
1349
+ "wee.my",
1350
+ "weg-werf-email.de",
1351
+ "wegwerf-emails.de",
1352
+ "wegwerfadresse.de",
1353
+ "wegwerfemail.com",
1354
+ "wegwerfemail.de",
1355
+ "wegwerfmail.de",
1356
+ "wegwerfmail.info",
1357
+ "wegwerfmail.net",
1358
+ "wegwerfmail.org",
1359
+ "wetrainbayarea.com",
1360
+ "wetrainbayarea.org",
1361
+ "wh4f.org",
1362
+ "whatiaas.com",
1363
+ "whatpaas.com",
1364
+ "whopy.com",
1365
+ "whtjddn.33mail.com",
1366
+ "whyspam.me",
1367
+ "willhackforfood.biz",
1368
+ "willselfdestruct.com",
1369
+ "winemaven.info",
1370
+ "wolfsmail.tk",
1371
+ "wollan.info",
1372
+ "worldspace.link",
1373
+ "wpdork.com",
1374
+ "wronghead.com",
1375
+ "wuzup.net",
1376
+ "wuzupmail.net",
1377
+ "wwwnew.eu",
1378
+ "xagloo.co",
1379
+ "xagloo.com",
1380
+ "xemaps.com",
1381
+ "xents.com",
1382
+ "xmaily.com",
1383
+ "xoxy.net",
1384
+ "yapped.net",
1385
+ "yep.it",
1386
+ "yogamaven.com",
1387
+ "yomail.info",
1388
+ "yopmail.com",
1389
+ "yopmail.fr",
1390
+ "yopmail.gq",
1391
+ "yopmail.net",
1392
+ "you-spam.com",
1393
+ "yourdomain.com",
1394
+ "ypmail.webarnak.fr.eu.org",
1395
+ "yuurok.com",
1396
+ "za.com",
1397
+ "zehnminuten.de",
1398
+ "zehnminutenmail.de",
1399
+ "zippymail.info",
1400
+ "zoaxe.com",
1401
+ "zoemail.com",
1402
+ "zoemail.net",
1403
+ "zoemail.org",
1404
+ "zomg.info",
1405
+ "zxcv.com",
1406
+ "zxcvbnm.com",
1407
+ "zzz.com"
1408
+ ]);
1409
+ function isDisposableDomain(domain) {
1410
+ return disposableDomains.has(domain.toLowerCase());
1411
+ }
1412
+
1413
+ // src/data/roles.ts
1414
+ var rolePrefixes = /* @__PURE__ */ new Set([
1415
+ // Administrative
1416
+ "admin",
1417
+ "administrator",
1418
+ "postmaster",
1419
+ "hostmaster",
1420
+ "webmaster",
1421
+ "root",
1422
+ "sysadmin",
1423
+ "it",
1424
+ "tech",
1425
+ "technical",
1426
+ // Support
1427
+ "support",
1428
+ "help",
1429
+ "helpdesk",
1430
+ "customerservice",
1431
+ "customer-service",
1432
+ "customercare",
1433
+ "customer-care",
1434
+ "service",
1435
+ "assist",
1436
+ "assistance",
1437
+ // Sales and Marketing
1438
+ "sales",
1439
+ "marketing",
1440
+ "advertising",
1441
+ "ads",
1442
+ "promo",
1443
+ "promotions",
1444
+ "partners",
1445
+ "partnership",
1446
+ "partnerships",
1447
+ "affiliate",
1448
+ "affiliates",
1449
+ "leads",
1450
+ "enquiry",
1451
+ "enquiries",
1452
+ "inquiry",
1453
+ "inquiries",
1454
+ // General Contact
1455
+ "info",
1456
+ "information",
1457
+ "contact",
1458
+ "contactus",
1459
+ "contact-us",
1460
+ "hello",
1461
+ "hi",
1462
+ "hey",
1463
+ "mail",
1464
+ "email",
1465
+ "office",
1466
+ "general",
1467
+ "reception",
1468
+ // Human Resources
1469
+ "hr",
1470
+ "humanresources",
1471
+ "human-resources",
1472
+ "jobs",
1473
+ "careers",
1474
+ "career",
1475
+ "recruitment",
1476
+ "recruiting",
1477
+ "talent",
1478
+ "hiring",
1479
+ "resume",
1480
+ "resumes",
1481
+ "cv",
1482
+ "employment",
1483
+ // Finance and Legal
1484
+ "billing",
1485
+ "finance",
1486
+ "accounting",
1487
+ "accounts",
1488
+ "payroll",
1489
+ "invoices",
1490
+ "invoice",
1491
+ "payments",
1492
+ "payment",
1493
+ "legal",
1494
+ "compliance",
1495
+ "privacy",
1496
+ "gdpr",
1497
+ // Media and PR
1498
+ "press",
1499
+ "media",
1500
+ "pr",
1501
+ "publicrelations",
1502
+ "public-relations",
1503
+ "news",
1504
+ "newsroom",
1505
+ "communications",
1506
+ "comms",
1507
+ // Security
1508
+ "security",
1509
+ "abuse",
1510
+ "spam",
1511
+ "phishing",
1512
+ "fraud",
1513
+ "noc",
1514
+ "cert",
1515
+ // Operations
1516
+ "operations",
1517
+ "ops",
1518
+ "devops",
1519
+ "engineering",
1520
+ "dev",
1521
+ "development",
1522
+ "product",
1523
+ "feedback",
1524
+ // Team/Group addresses
1525
+ "team",
1526
+ "staff",
1527
+ "all",
1528
+ "everyone",
1529
+ "group",
1530
+ "department",
1531
+ "board",
1532
+ "management",
1533
+ "exec",
1534
+ "executive",
1535
+ "executives",
1536
+ // E-commerce
1537
+ "orders",
1538
+ "order",
1539
+ "shipping",
1540
+ "returns",
1541
+ "refunds",
1542
+ "shop",
1543
+ "store",
1544
+ "buy",
1545
+ "purchase",
1546
+ "checkout",
1547
+ // Automated/System
1548
+ "noreply",
1549
+ "no-reply",
1550
+ "donotreply",
1551
+ "do-not-reply",
1552
+ "mailer-daemon",
1553
+ "mailerdaemon",
1554
+ "bounce",
1555
+ "bounces",
1556
+ "auto",
1557
+ "autoresponder",
1558
+ "notifications",
1559
+ "notification",
1560
+ "alerts",
1561
+ "alert",
1562
+ "updates",
1563
+ "newsletter",
1564
+ "newsletters",
1565
+ "subscribe",
1566
+ "unsubscribe",
1567
+ // Miscellaneous
1568
+ "ftp",
1569
+ "www",
1570
+ "web",
1571
+ "api",
1572
+ "test",
1573
+ "testing",
1574
+ "demo",
1575
+ "sample",
1576
+ "example"
1577
+ ]);
1578
+ function isRolePrefix(prefix) {
1579
+ return rolePrefixes.has(prefix.toLowerCase());
1580
+ }
1581
+
1582
+ // src/data/free-providers.ts
1583
+ var freeProviders = /* @__PURE__ */ new Set([
1584
+ // Google
1585
+ "gmail.com",
1586
+ "googlemail.com",
1587
+ // Microsoft
1588
+ "outlook.com",
1589
+ "outlook.co.uk",
1590
+ "outlook.com.br",
1591
+ "outlook.com.au",
1592
+ "outlook.de",
1593
+ "outlook.fr",
1594
+ "outlook.it",
1595
+ "outlook.es",
1596
+ "outlook.jp",
1597
+ "hotmail.com",
1598
+ "hotmail.co.uk",
1599
+ "hotmail.com.br",
1600
+ "hotmail.com.au",
1601
+ "hotmail.de",
1602
+ "hotmail.fr",
1603
+ "hotmail.it",
1604
+ "hotmail.es",
1605
+ "hotmail.co.jp",
1606
+ "live.com",
1607
+ "live.co.uk",
1608
+ "live.com.au",
1609
+ "live.de",
1610
+ "live.fr",
1611
+ "live.it",
1612
+ "live.nl",
1613
+ "live.se",
1614
+ "msn.com",
1615
+ // Yahoo
1616
+ "yahoo.com",
1617
+ "yahoo.co.uk",
1618
+ "yahoo.com.br",
1619
+ "yahoo.com.au",
1620
+ "yahoo.de",
1621
+ "yahoo.fr",
1622
+ "yahoo.it",
1623
+ "yahoo.es",
1624
+ "yahoo.co.jp",
1625
+ "yahoo.co.in",
1626
+ "yahoo.ca",
1627
+ "yahoo.com.mx",
1628
+ "yahoo.com.ar",
1629
+ "yahoo.com.sg",
1630
+ "yahoo.co.id",
1631
+ "yahoo.com.ph",
1632
+ "yahoo.com.tw",
1633
+ "yahoo.com.hk",
1634
+ "yahoo.ie",
1635
+ "yahoo.co.nz",
1636
+ "yahoo.com.vn",
1637
+ "ymail.com",
1638
+ "rocketmail.com",
1639
+ // Apple
1640
+ "icloud.com",
1641
+ "me.com",
1642
+ "mac.com",
1643
+ // AOL
1644
+ "aol.com",
1645
+ "aol.co.uk",
1646
+ "aol.de",
1647
+ "aol.fr",
1648
+ "aim.com",
1649
+ // Proton
1650
+ "protonmail.com",
1651
+ "protonmail.ch",
1652
+ "proton.me",
1653
+ "pm.me",
1654
+ // Zoho
1655
+ "zoho.com",
1656
+ "zohomail.com",
1657
+ "zohomail.in",
1658
+ "zohomail.eu",
1659
+ // Mail.com
1660
+ "mail.com",
1661
+ "email.com",
1662
+ "usa.com",
1663
+ "myself.com",
1664
+ "consultant.com",
1665
+ "post.com",
1666
+ "europe.com",
1667
+ "asia.com",
1668
+ "iname.com",
1669
+ "writeme.com",
1670
+ "dr.com",
1671
+ "cheerful.com",
1672
+ "accountant.com",
1673
+ "techie.com",
1674
+ "engineer.com",
1675
+ "activist.com",
1676
+ "contractor.com",
1677
+ "artlover.com",
1678
+ // GMX
1679
+ "gmx.com",
1680
+ "gmx.net",
1681
+ "gmx.de",
1682
+ "gmx.at",
1683
+ "gmx.ch",
1684
+ "gmx.fr",
1685
+ "gmx.co.uk",
1686
+ "gmx.us",
1687
+ // Tutanota/Tuta
1688
+ "tutanota.com",
1689
+ "tutanota.de",
1690
+ "tutamail.com",
1691
+ "tuta.io",
1692
+ "keemail.me",
1693
+ // Fastmail
1694
+ "fastmail.com",
1695
+ "fastmail.fm",
1696
+ // Mailfence
1697
+ "mailfence.com",
1698
+ // Hushmail
1699
+ "hushmail.com",
1700
+ "hush.com",
1701
+ "hush.ai",
1702
+ "mac.hush.com",
1703
+ // Runbox
1704
+ "runbox.com",
1705
+ // Posteo
1706
+ "posteo.de",
1707
+ "posteo.net",
1708
+ // Yandex
1709
+ "yandex.com",
1710
+ "yandex.ru",
1711
+ "yandex.ua",
1712
+ "yandex.by",
1713
+ "yandex.kz",
1714
+ "ya.ru",
1715
+ // Mail.ru
1716
+ "mail.ru",
1717
+ "inbox.ru",
1718
+ "list.ru",
1719
+ "bk.ru",
1720
+ // Chinese providers
1721
+ "qq.com",
1722
+ "163.com",
1723
+ "126.com",
1724
+ "sina.com",
1725
+ "sina.cn",
1726
+ "sohu.com",
1727
+ "aliyun.com",
1728
+ "foxmail.com",
1729
+ // Indian providers
1730
+ "rediffmail.com",
1731
+ "rediff.com",
1732
+ // European providers
1733
+ "web.de",
1734
+ "t-online.de",
1735
+ "freenet.de",
1736
+ "arcor.de",
1737
+ "orange.fr",
1738
+ "laposte.net",
1739
+ "free.fr",
1740
+ "sfr.fr",
1741
+ "wanadoo.fr",
1742
+ "libero.it",
1743
+ "virgilio.it",
1744
+ "tin.it",
1745
+ "alice.it",
1746
+ "tiscali.it",
1747
+ "tiscali.co.uk",
1748
+ "terra.com.br",
1749
+ "uol.com.br",
1750
+ "bol.com.br",
1751
+ "ig.com.br",
1752
+ "wp.pl",
1753
+ "onet.pl",
1754
+ "interia.pl",
1755
+ "o2.pl",
1756
+ "seznam.cz",
1757
+ "centrum.cz",
1758
+ "azet.sk",
1759
+ // Misc
1760
+ "inbox.com",
1761
+ "lycos.com",
1762
+ "naver.com",
1763
+ "daum.net",
1764
+ "hanmail.net",
1765
+ "comcast.net",
1766
+ "verizon.net",
1767
+ "att.net",
1768
+ "sbcglobal.net",
1769
+ "bellsouth.net",
1770
+ "charter.net",
1771
+ "cox.net",
1772
+ "earthlink.net",
1773
+ "juno.com",
1774
+ "netzero.net",
1775
+ "optonline.net",
1776
+ "btinternet.com",
1777
+ "talktalk.net",
1778
+ "virginmedia.com",
1779
+ "sky.com",
1780
+ "ntlworld.com",
1781
+ "shaw.ca",
1782
+ "rogers.com",
1783
+ "sympatico.ca",
1784
+ "telus.net",
1785
+ "bigpond.com",
1786
+ "bigpond.net.au",
1787
+ "optusnet.com.au",
1788
+ "ozemail.com.au"
1789
+ ]);
1790
+ function isFreeProvider(domain) {
1791
+ return freeProviders.has(domain.toLowerCase());
1792
+ }
1793
+
1794
+ // src/validators/misc.ts
1795
+ function checkDisposable(domain) {
1796
+ return isDisposableDomain(domain.toLowerCase());
1797
+ }
1798
+ function checkRoleAccount(username) {
1799
+ const cleanUsername = username.split("+")[0];
1800
+ return isRolePrefix(cleanUsername.toLowerCase());
1801
+ }
1802
+ function checkFreeProvider(domain) {
1803
+ return isFreeProvider(domain.toLowerCase());
1804
+ }
1805
+ function md5(str) {
1806
+ return import_crypto.default.createHash("md5").update(str.trim().toLowerCase()).digest("hex");
1807
+ }
1808
+ function checkGravatar(email) {
1809
+ return new Promise((resolve) => {
1810
+ const hash = md5(email);
1811
+ const url = `https://www.gravatar.com/avatar/${hash}?d=404`;
1812
+ const req = import_https.default.get(url, { timeout: 5e3 }, (res) => {
1813
+ if (res.statusCode === 200) {
1814
+ resolve(`https://www.gravatar.com/avatar/${hash}`);
1815
+ } else {
1816
+ resolve(null);
1817
+ }
1818
+ res.resume();
1819
+ });
1820
+ req.on("error", () => {
1821
+ resolve(null);
1822
+ });
1823
+ req.on("timeout", () => {
1824
+ req.destroy();
1825
+ resolve(null);
1826
+ });
1827
+ });
1828
+ }
1829
+ async function checkMisc(email, options = {}) {
1830
+ const atIndex = email.indexOf("@");
1831
+ if (atIndex === -1) {
1832
+ return {
1833
+ isDisposable: false,
1834
+ isRoleAccount: false,
1835
+ isFreeProvider: false,
1836
+ gravatarUrl: null
1837
+ };
1838
+ }
1839
+ const username = email.slice(0, atIndex);
1840
+ const domain = email.slice(atIndex + 1);
1841
+ const isDisposable = options.checkDisposable !== false ? checkDisposable(domain) : false;
1842
+ const isRoleAccount = options.checkRoleAccount !== false ? checkRoleAccount(username) : false;
1843
+ const isFree = options.checkFreeProvider !== false ? checkFreeProvider(domain) : false;
1844
+ let gravatarUrl = null;
1845
+ if (options.checkGravatar === true) {
1846
+ gravatarUrl = await checkGravatar(email);
1847
+ }
1848
+ return {
1849
+ isDisposable,
1850
+ isRoleAccount,
1851
+ isFreeProvider: isFree,
1852
+ gravatarUrl
1853
+ };
1854
+ }
1855
+
1856
+ // src/index.ts
1857
+ var defaultOptions = {
1858
+ smtpTimeout: 1e4,
1859
+ verifySmtp: true,
1860
+ checkDisposable: true,
1861
+ checkRoleAccount: true,
1862
+ checkFreeProvider: true,
1863
+ checkGravatar: false,
1864
+ heloHost: "",
1865
+ senderAddress: "",
1866
+ dnsCacheTtl: 3e5,
1867
+ useDnsCache: true,
1868
+ detectCatchAll: true,
1869
+ proxy: void 0
1870
+ };
1871
+ function determineReachability(syntaxValid, mxValid, smtpResult, options) {
1872
+ if (!syntaxValid) {
1873
+ return "invalid";
1874
+ }
1875
+ if (!mxValid) {
1876
+ return "invalid";
1877
+ }
1878
+ if (options.verifySmtp === false) {
1879
+ return "risky";
1880
+ }
1881
+ if (!smtpResult) {
1882
+ return "unknown";
1883
+ }
1884
+ if (smtpResult.isCatchAll) {
1885
+ return "risky";
1886
+ }
1887
+ if (smtpResult.isDeliverable) {
1888
+ return "safe";
1889
+ }
1890
+ if (!smtpResult.canConnectSmtp) {
1891
+ return "unknown";
1892
+ }
1893
+ if (smtpResult.error?.type === SmtpErrorType.MAILBOX_NOT_FOUND) {
1894
+ return "invalid";
1895
+ }
1896
+ if (smtpResult.error?.type === SmtpErrorType.GREYLISTED) {
1897
+ return "risky";
1898
+ }
1899
+ return "risky";
1900
+ }
1901
+ async function validate(email, options = {}) {
1902
+ const opts = { ...defaultOptions, ...options };
1903
+ if (opts.useDnsCache && opts.dnsCacheTtl) {
1904
+ globalDnsCache.setTtl(opts.dnsCacheTtl);
1905
+ }
1906
+ const syntaxResult = validateSyntax(email);
1907
+ if (!syntaxResult.isValidSyntax) {
1908
+ return {
1909
+ input: email,
1910
+ isReachable: "invalid",
1911
+ syntax: syntaxResult,
1912
+ mx: {
1913
+ acceptsMail: false,
1914
+ records: []
1915
+ },
1916
+ smtp: {
1917
+ canConnectSmtp: false,
1918
+ isDeliverable: false,
1919
+ isCatchAll: false,
1920
+ error: null
1921
+ },
1922
+ misc: {
1923
+ isDisposable: false,
1924
+ isRoleAccount: false,
1925
+ isFreeProvider: false,
1926
+ gravatarUrl: null
1927
+ }
1928
+ };
1929
+ }
1930
+ const mxResult = await verifyMx(syntaxResult.domain, opts);
1931
+ if (!mxResult.acceptsMail) {
1932
+ const miscResult2 = await checkMisc(email, opts);
1933
+ return {
1934
+ input: email,
1935
+ isReachable: "invalid",
1936
+ syntax: syntaxResult,
1937
+ mx: mxResult,
1938
+ smtp: {
1939
+ canConnectSmtp: false,
1940
+ isDeliverable: false,
1941
+ isCatchAll: false,
1942
+ error: {
1943
+ type: "NoMxRecords",
1944
+ message: "Domain does not accept mail"
1945
+ }
1946
+ },
1947
+ misc: miscResult2
1948
+ };
1949
+ }
1950
+ let smtpResult = {
1951
+ canConnectSmtp: false,
1952
+ isDeliverable: false,
1953
+ isCatchAll: false,
1954
+ error: null
1955
+ };
1956
+ if (opts.verifySmtp) {
1957
+ smtpResult = await verifySmtpWithFallback(email, mxResult.records, opts);
1958
+ }
1959
+ const miscResult = await checkMisc(email, opts);
1960
+ const isReachable = determineReachability(
1961
+ syntaxResult.isValidSyntax,
1962
+ mxResult.acceptsMail,
1963
+ smtpResult,
1964
+ opts
1965
+ );
1966
+ return {
1967
+ input: email,
1968
+ isReachable,
1969
+ syntax: syntaxResult,
1970
+ mx: mxResult,
1971
+ smtp: smtpResult,
1972
+ misc: miscResult
1973
+ };
1974
+ }
1975
+ async function validateBulk(emails, options = {}) {
1976
+ const { concurrency = 5, delayBetween = 0, onProgress, ...validateOptions } = options;
1977
+ const results = [];
1978
+ let completed = 0;
1979
+ for (let i = 0; i < emails.length; i += concurrency) {
1980
+ const batch = emails.slice(i, i + concurrency);
1981
+ const batchPromises = batch.map(async (email) => {
1982
+ const result = await validate(email, validateOptions);
1983
+ completed++;
1984
+ if (onProgress) {
1985
+ onProgress(completed, emails.length, result);
1986
+ }
1987
+ return result;
1988
+ });
1989
+ const batchResults = await Promise.all(batchPromises);
1990
+ results.push(...batchResults);
1991
+ if (delayBetween > 0 && i + concurrency < emails.length) {
1992
+ await new Promise((resolve) => setTimeout(resolve, delayBetween));
1993
+ }
1994
+ }
1995
+ return results;
1996
+ }
1997
+ async function validateQuick(email) {
1998
+ return validate(email, { verifySmtp: false });
1999
+ }
2000
+
2001
+ // bin/cli.ts
2002
+ function parseArgs(args) {
2003
+ const result = {
2004
+ emails: [],
2005
+ quick: false,
2006
+ pretty: false,
2007
+ verbose: false,
2008
+ timeout: 1e4,
2009
+ concurrency: 5,
2010
+ noSmtp: false,
2011
+ noDisposable: false,
2012
+ noRole: false,
2013
+ noFreeProvider: false,
2014
+ gravatar: false,
2015
+ help: false,
2016
+ version: false
2017
+ };
2018
+ for (let i = 0; i < args.length; i++) {
2019
+ const arg = args[i];
2020
+ switch (arg) {
2021
+ case "-h":
2022
+ case "--help":
2023
+ result.help = true;
2024
+ break;
2025
+ case "-v":
2026
+ case "--version":
2027
+ result.version = true;
2028
+ break;
2029
+ case "-q":
2030
+ case "--quick":
2031
+ result.quick = true;
2032
+ break;
2033
+ case "-p":
2034
+ case "--pretty":
2035
+ result.pretty = true;
2036
+ break;
2037
+ case "--verbose":
2038
+ result.verbose = true;
2039
+ break;
2040
+ case "-f":
2041
+ case "--file":
2042
+ result.file = args[++i];
2043
+ break;
2044
+ case "-o":
2045
+ case "--output":
2046
+ result.output = args[++i];
2047
+ break;
2048
+ case "-t":
2049
+ case "--timeout":
2050
+ result.timeout = parseInt(args[++i], 10);
2051
+ break;
2052
+ case "-c":
2053
+ case "--concurrency":
2054
+ result.concurrency = parseInt(args[++i], 10);
2055
+ break;
2056
+ case "--no-smtp":
2057
+ result.noSmtp = true;
2058
+ break;
2059
+ case "--no-disposable":
2060
+ result.noDisposable = true;
2061
+ break;
2062
+ case "--no-role":
2063
+ result.noRole = true;
2064
+ break;
2065
+ case "--no-free-provider":
2066
+ result.noFreeProvider = true;
2067
+ break;
2068
+ case "--gravatar":
2069
+ result.gravatar = true;
2070
+ break;
2071
+ default:
2072
+ if (!arg.startsWith("-")) {
2073
+ result.emails.push(arg);
2074
+ }
2075
+ }
2076
+ }
2077
+ return result;
2078
+ }
2079
+ function printHelp() {
2080
+ console.log(`
2081
+ bouncevalidator - Local email validation service
2082
+
2083
+ USAGE:
2084
+ bouncevalidator [OPTIONS] <email>...
2085
+ bouncevalidator -f <file> [OPTIONS]
2086
+
2087
+ ARGUMENTS:
2088
+ <email>... Email addresses to validate
2089
+
2090
+ OPTIONS:
2091
+ -h, --help Show this help message
2092
+ -v, --version Show version number
2093
+ -f, --file <path> Read emails from file (one per line)
2094
+ -o, --output <path> Write results to file
2095
+ -q, --quick Quick mode (skip SMTP verification)
2096
+ -p, --pretty Pretty print JSON output
2097
+ --verbose Show detailed progress
2098
+
2099
+ VALIDATION OPTIONS:
2100
+ -t, --timeout <ms> SMTP timeout in milliseconds (default: 10000)
2101
+ -c, --concurrency <n> Max concurrent validations (default: 5)
2102
+ --no-smtp Skip SMTP verification
2103
+ --no-disposable Skip disposable email check
2104
+ --no-role Skip role account check
2105
+ --no-free-provider Skip free provider check
2106
+ --gravatar Check for Gravatar
2107
+
2108
+ EXAMPLES:
2109
+ bouncevalidator user@example.com
2110
+ bouncevalidator -p user@example.com user2@example.com
2111
+ bouncevalidator -f emails.txt -o results.json
2112
+ bouncevalidator --quick user@example.com
2113
+
2114
+ EXIT CODES:
2115
+ 0 All emails are valid (safe)
2116
+ 1 One or more emails are invalid
2117
+ 2 One or more emails are risky or unknown
2118
+ 3 Error occurred during validation
2119
+ `);
2120
+ }
2121
+ function printVersion() {
2122
+ console.log("bouncevalidator v1.0.0");
2123
+ }
2124
+ async function readEmailsFromFile(filePath) {
2125
+ const emails = [];
2126
+ const fileStream = import_fs.default.createReadStream(filePath);
2127
+ const rl = import_readline.default.createInterface({
2128
+ input: fileStream,
2129
+ crlfDelay: Infinity
2130
+ });
2131
+ for await (const line of rl) {
2132
+ const trimmed = line.trim();
2133
+ if (trimmed && !trimmed.startsWith("#")) {
2134
+ emails.push(trimmed);
2135
+ }
2136
+ }
2137
+ return emails;
2138
+ }
2139
+ function formatResult(result, pretty) {
2140
+ return JSON.stringify(result, null, pretty ? 2 : 0);
2141
+ }
2142
+ function getExitCode(results) {
2143
+ let hasInvalid = false;
2144
+ let hasRisky = false;
2145
+ for (const result of results) {
2146
+ if (result.isReachable === "invalid") {
2147
+ hasInvalid = true;
2148
+ } else if (result.isReachable === "risky" || result.isReachable === "unknown") {
2149
+ hasRisky = true;
2150
+ }
2151
+ }
2152
+ if (hasInvalid) return 1;
2153
+ if (hasRisky) return 2;
2154
+ return 0;
2155
+ }
2156
+ async function main() {
2157
+ const args = parseArgs(process.argv.slice(2));
2158
+ if (args.help) {
2159
+ printHelp();
2160
+ process.exit(0);
2161
+ }
2162
+ if (args.version) {
2163
+ printVersion();
2164
+ process.exit(0);
2165
+ }
2166
+ let emails = [...args.emails];
2167
+ if (args.file) {
2168
+ try {
2169
+ const fileEmails = await readEmailsFromFile(args.file);
2170
+ emails = [...emails, ...fileEmails];
2171
+ } catch (err) {
2172
+ const error = err;
2173
+ console.error(`Error reading file: ${error.message}`);
2174
+ process.exit(3);
2175
+ }
2176
+ }
2177
+ if (emails.length === 0) {
2178
+ console.error("Error: No email addresses provided");
2179
+ console.error("Use --help for usage information");
2180
+ process.exit(3);
2181
+ }
2182
+ const options = {
2183
+ smtpTimeout: args.timeout,
2184
+ verifySmtp: !args.noSmtp && !args.quick,
2185
+ checkDisposable: !args.noDisposable,
2186
+ checkRoleAccount: !args.noRole,
2187
+ checkFreeProvider: !args.noFreeProvider,
2188
+ checkGravatar: args.gravatar
2189
+ };
2190
+ try {
2191
+ let results;
2192
+ if (emails.length === 1) {
2193
+ if (args.verbose) {
2194
+ console.error(`Validating: ${emails[0]}`);
2195
+ }
2196
+ const result = args.quick ? await validateQuick(emails[0]) : await validate(emails[0], options);
2197
+ results = [result];
2198
+ } else {
2199
+ if (args.verbose) {
2200
+ console.error(`Validating ${emails.length} emails...`);
2201
+ }
2202
+ results = await validateBulk(emails, {
2203
+ ...options,
2204
+ concurrency: args.concurrency,
2205
+ onProgress: args.verbose ? (completed, total, result) => {
2206
+ console.error(`[${completed}/${total}] ${result.input}: ${result.isReachable}`);
2207
+ } : void 0
2208
+ });
2209
+ }
2210
+ const output = results.length === 1 ? formatResult(results[0], args.pretty) : JSON.stringify(results, null, args.pretty ? 2 : 0);
2211
+ if (args.output) {
2212
+ import_fs.default.writeFileSync(args.output, output + "\n");
2213
+ if (args.verbose) {
2214
+ console.error(`Results written to: ${args.output}`);
2215
+ }
2216
+ } else {
2217
+ console.log(output);
2218
+ }
2219
+ process.exit(getExitCode(results));
2220
+ } catch (err) {
2221
+ const error = err;
2222
+ console.error(`Error: ${error.message}`);
2223
+ process.exit(3);
2224
+ }
2225
+ }
2226
+ main().catch((err) => {
2227
+ console.error(`Fatal error: ${err.message}`);
2228
+ process.exit(3);
2229
+ });