@emailcheck/email-validator-js 2.14.2 → 3.0.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +279 -18
- package/dist/adapters/lru-adapter.d.ts +2 -2
- package/dist/adapters/redis-adapter.d.ts +4 -4
- package/dist/batch-verifier.d.ts +5 -0
- package/dist/cache-interface.d.ts +23 -15
- package/dist/cache.d.ts +7 -5
- package/dist/check-if-email-exists.d.ts +205 -0
- package/dist/domain-suggester.d.ts +6 -6
- package/dist/{validator.d.ts → email-validator.d.ts} +2 -2
- package/dist/email-verifier-types.d.ts +225 -0
- package/dist/index.d.ts +8 -8
- package/dist/index.esm.js +779 -266
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +783 -268
- package/dist/index.js.map +1 -1
- package/dist/mx-resolver.d.ts +2 -0
- package/dist/name-detector.d.ts +6 -6
- package/dist/serverless/adapters/aws-lambda.cjs.js.map +1 -1
- package/dist/serverless/adapters/aws-lambda.esm.js.map +1 -1
- package/dist/serverless/adapters/cloudflare.cjs.js.map +1 -1
- package/dist/serverless/adapters/cloudflare.esm.js.map +1 -1
- package/dist/serverless/adapters/vercel.cjs.js.map +1 -1
- package/dist/serverless/adapters/vercel.esm.js.map +1 -1
- package/dist/serverless/index.cjs.js.map +1 -1
- package/dist/serverless/index.d.ts +1 -1
- package/dist/serverless/index.esm.js.map +1 -1
- package/dist/serverless/{core.cjs.js → verifier.cjs.js} +1 -1
- package/dist/serverless/verifier.cjs.js.map +1 -0
- package/dist/serverless/{core.esm.js → verifier.esm.js} +1 -1
- package/dist/serverless/verifier.esm.js.map +1 -0
- package/dist/smtp-verifier.d.ts +7 -0
- package/dist/types.d.ts +170 -28
- package/dist/whois.d.ts +3 -3
- package/package.json +19 -19
- package/dist/batch.d.ts +0 -5
- package/dist/dns.d.ts +0 -2
- package/dist/serverless/core.cjs.js.map +0 -1
- package/dist/serverless/core.esm.js.map +0 -1
- package/dist/smtp.d.ts +0 -2
- /package/dist/serverless/{core.d.ts → verifier.d.ts} +0 -0
- /package/dist/serverless/{core.min.js → verifier.min.js} +0 -0
package/dist/index.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
var psl = require('psl');
|
|
4
4
|
var tinyLru = require('tiny-lru');
|
|
5
|
-
var node_dns = require('node:dns');
|
|
6
5
|
var stringSimilarityJs = require('string-similarity-js');
|
|
6
|
+
var node_dns = require('node:dns');
|
|
7
7
|
var net = require('node:net');
|
|
8
|
+
var tls = require('node:tls');
|
|
8
9
|
|
|
9
10
|
function _interopNamespaceDefault(e) {
|
|
10
11
|
var n = Object.create(null);
|
|
@@ -24,6 +25,7 @@ function _interopNamespaceDefault(e) {
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
var net__namespace = /*#__PURE__*/_interopNamespaceDefault(net);
|
|
28
|
+
var tls__namespace = /*#__PURE__*/_interopNamespaceDefault(tls);
|
|
27
29
|
|
|
28
30
|
class LRUAdapter {
|
|
29
31
|
constructor(maxSize = 1e3, ttlMs = 36e5) {
|
|
@@ -64,28 +66,23 @@ class LRUAdapter {
|
|
|
64
66
|
const DEFAULT_CACHE_OPTIONS = {
|
|
65
67
|
ttl: {
|
|
66
68
|
mx: 36e5,
|
|
67
|
-
// 1 hour
|
|
68
69
|
disposable: 864e5,
|
|
69
|
-
// 24 hours
|
|
70
70
|
free: 864e5,
|
|
71
|
-
// 24 hours
|
|
72
71
|
domainValid: 864e5,
|
|
73
|
-
// 24 hours
|
|
74
72
|
smtp: 18e5,
|
|
75
|
-
|
|
73
|
+
smtpPort: 864e5,
|
|
76
74
|
domainSuggestion: 864e5,
|
|
77
|
-
// 24 hours
|
|
78
75
|
whois: 36e5
|
|
79
|
-
// 1 hour
|
|
80
76
|
},
|
|
81
77
|
maxSize: {
|
|
82
|
-
mx:
|
|
83
|
-
disposable:
|
|
84
|
-
free:
|
|
85
|
-
domainValid:
|
|
86
|
-
smtp:
|
|
87
|
-
|
|
88
|
-
|
|
78
|
+
mx: 1e4,
|
|
79
|
+
disposable: 1e4,
|
|
80
|
+
free: 1e4,
|
|
81
|
+
domainValid: 1e4,
|
|
82
|
+
smtp: 1e4,
|
|
83
|
+
smtpPort: 1e4,
|
|
84
|
+
domainSuggestion: 1e4,
|
|
85
|
+
whois: 1e4
|
|
89
86
|
}
|
|
90
87
|
};
|
|
91
88
|
let defaultCacheInstance = null;
|
|
@@ -97,6 +94,7 @@ function getDefaultCache() {
|
|
|
97
94
|
free: new LRUAdapter(DEFAULT_CACHE_OPTIONS.maxSize.free, DEFAULT_CACHE_OPTIONS.ttl.free),
|
|
98
95
|
domainValid: new LRUAdapter(DEFAULT_CACHE_OPTIONS.maxSize.domainValid, DEFAULT_CACHE_OPTIONS.ttl.domainValid),
|
|
99
96
|
smtp: new LRUAdapter(DEFAULT_CACHE_OPTIONS.maxSize.smtp, DEFAULT_CACHE_OPTIONS.ttl.smtp),
|
|
97
|
+
smtpPort: new LRUAdapter(DEFAULT_CACHE_OPTIONS.maxSize.smtpPort, DEFAULT_CACHE_OPTIONS.ttl.smtpPort),
|
|
100
98
|
domainSuggestion: new LRUAdapter(DEFAULT_CACHE_OPTIONS.maxSize.domainSuggestion, DEFAULT_CACHE_OPTIONS.ttl.domainSuggestion),
|
|
101
99
|
whois: new LRUAdapter(DEFAULT_CACHE_OPTIONS.maxSize.whois, DEFAULT_CACHE_OPTIONS.ttl.whois)
|
|
102
100
|
};
|
|
@@ -113,6 +111,7 @@ function clearDefaultCache() {
|
|
|
113
111
|
defaultCacheInstance.free.clear();
|
|
114
112
|
defaultCacheInstance.domainValid.clear();
|
|
115
113
|
defaultCacheInstance.smtp.clear();
|
|
114
|
+
defaultCacheInstance.smtpPort.clear();
|
|
116
115
|
defaultCacheInstance.domainSuggestion.clear();
|
|
117
116
|
defaultCacheInstance.whois.clear();
|
|
118
117
|
}
|
|
@@ -121,41 +120,7 @@ function resetDefaultCache() {
|
|
|
121
120
|
defaultCacheInstance = null;
|
|
122
121
|
}
|
|
123
122
|
|
|
124
|
-
|
|
125
|
-
const { domain, cache, logger } = params;
|
|
126
|
-
const log = logger || (() => {
|
|
127
|
-
});
|
|
128
|
-
const cacheStore = getCacheStore(cache, "mx");
|
|
129
|
-
const cached = await cacheStore.get(domain);
|
|
130
|
-
if (cached !== null && cached !== void 0) {
|
|
131
|
-
log(`[resolveMxRecords] Cache hit for ${domain}: ${cached.length} MX records`);
|
|
132
|
-
return cached;
|
|
133
|
-
}
|
|
134
|
-
log(`[resolveMxRecords] Performing DNS MX lookup for ${domain}`);
|
|
135
|
-
try {
|
|
136
|
-
const records = await node_dns.promises.resolveMx(domain);
|
|
137
|
-
records.sort((a, b) => {
|
|
138
|
-
if (a.priority < b.priority) {
|
|
139
|
-
return -1;
|
|
140
|
-
}
|
|
141
|
-
if (a.priority > b.priority) {
|
|
142
|
-
return 1;
|
|
143
|
-
}
|
|
144
|
-
return 0;
|
|
145
|
-
});
|
|
146
|
-
const exchanges = records.map((record) => record.exchange);
|
|
147
|
-
log(`[resolveMxRecords] Found ${exchanges.length} MX records for ${domain}: [${exchanges.join(", ")}]`);
|
|
148
|
-
await cacheStore.set(domain, exchanges);
|
|
149
|
-
log(`[resolveMxRecords] Cached ${exchanges.length} MX records for ${domain}`);
|
|
150
|
-
return exchanges;
|
|
151
|
-
} catch (error) {
|
|
152
|
-
log(`[resolveMxRecords] MX lookup failed for ${domain}, caching empty result`);
|
|
153
|
-
await cacheStore.set(domain, []);
|
|
154
|
-
throw error;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const COMMON_EMAIL_DOMAINS = [
|
|
123
|
+
const commonEmailDomains = [
|
|
159
124
|
// Popular free email providers
|
|
160
125
|
"gmail.com",
|
|
161
126
|
"yahoo.com",
|
|
@@ -240,7 +205,7 @@ function getSimilarityThreshold(domain) {
|
|
|
240
205
|
return 0.75;
|
|
241
206
|
}
|
|
242
207
|
function defaultDomainSuggestionMethod(domain, commonDomains) {
|
|
243
|
-
const domainsToCheck = commonDomains ||
|
|
208
|
+
const domainsToCheck = commonDomains || commonEmailDomains;
|
|
244
209
|
const lowerDomain = domain.toLowerCase();
|
|
245
210
|
if (domainsToCheck.includes(lowerDomain)) {
|
|
246
211
|
return null;
|
|
@@ -296,7 +261,7 @@ async function defaultDomainSuggestionMethodImpl(domain, commonDomains, cache) {
|
|
|
296
261
|
if (!domain || domain.length < 3) {
|
|
297
262
|
return null;
|
|
298
263
|
}
|
|
299
|
-
const domainsToCheck = commonDomains ||
|
|
264
|
+
const domainsToCheck = commonDomains || commonEmailDomains;
|
|
300
265
|
const lowerDomain = domain.toLowerCase();
|
|
301
266
|
const cacheKey = `${lowerDomain}:${domainsToCheck.length}`;
|
|
302
267
|
const cacheStore = getCacheStore(cache, "domainSuggestion");
|
|
@@ -392,15 +357,107 @@ async function suggestEmailDomain(email, commonDomains, cache) {
|
|
|
392
357
|
return null;
|
|
393
358
|
}
|
|
394
359
|
function isCommonDomain(domain, commonDomains) {
|
|
395
|
-
const domainsToCheck = commonDomains ||
|
|
360
|
+
const domainsToCheck = commonDomains || commonEmailDomains;
|
|
396
361
|
return domainsToCheck.includes(domain.toLowerCase());
|
|
397
362
|
}
|
|
398
363
|
function getDomainSimilarity(domain1, domain2) {
|
|
399
364
|
return stringSimilarityJs.stringSimilarity(domain1.toLowerCase(), domain2.toLowerCase());
|
|
400
365
|
}
|
|
401
366
|
|
|
402
|
-
|
|
403
|
-
|
|
367
|
+
async function isValidEmailDomain(emailOrDomain, cache) {
|
|
368
|
+
let [localPart, emailDomain] = (emailOrDomain === null || emailOrDomain === void 0 ? void 0 : emailOrDomain.split("@")) || [];
|
|
369
|
+
if (!emailDomain) {
|
|
370
|
+
emailDomain = localPart;
|
|
371
|
+
}
|
|
372
|
+
if (!emailDomain) {
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
const cacheStore = getCacheStore(cache, "domainValid");
|
|
376
|
+
const cached = await cacheStore.get(emailDomain);
|
|
377
|
+
if (cached !== null && cached !== void 0) {
|
|
378
|
+
return cached.isValid;
|
|
379
|
+
}
|
|
380
|
+
try {
|
|
381
|
+
const isValidResult = psl.isValid(emailDomain) || false;
|
|
382
|
+
const richResult = {
|
|
383
|
+
isValid: isValidResult,
|
|
384
|
+
hasMX: false,
|
|
385
|
+
// MX not checked in this function
|
|
386
|
+
checkedAt: Date.now()
|
|
387
|
+
};
|
|
388
|
+
await cacheStore.set(emailDomain, richResult);
|
|
389
|
+
return isValidResult;
|
|
390
|
+
} catch (validationError) {
|
|
391
|
+
const errorResult = {
|
|
392
|
+
isValid: false,
|
|
393
|
+
hasMX: false,
|
|
394
|
+
checkedAt: Date.now()
|
|
395
|
+
};
|
|
396
|
+
await cacheStore.set(emailDomain, errorResult);
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function isValidEmail(emailAddress) {
|
|
401
|
+
if (!emailAddress || typeof emailAddress !== "string") {
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
|
405
|
+
const emailLower = emailAddress.toLowerCase();
|
|
406
|
+
if (emailLower.indexOf(".+") !== -1)
|
|
407
|
+
return false;
|
|
408
|
+
if (emailLower.indexOf("..") !== -1)
|
|
409
|
+
return false;
|
|
410
|
+
if (emailLower.startsWith(".") || emailLower.endsWith("."))
|
|
411
|
+
return false;
|
|
412
|
+
const parts = emailAddress.split("@");
|
|
413
|
+
if (parts.length !== 2)
|
|
414
|
+
return false;
|
|
415
|
+
const [localPart, domain] = parts;
|
|
416
|
+
if (!localPart || !domain)
|
|
417
|
+
return false;
|
|
418
|
+
if (localPart.length > 64)
|
|
419
|
+
return false;
|
|
420
|
+
if (domain.length > 253)
|
|
421
|
+
return false;
|
|
422
|
+
return re.test(emailLower);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async function resolveMxRecords(params) {
|
|
426
|
+
const { domain, cache, logger } = params;
|
|
427
|
+
const log = logger || (() => {
|
|
428
|
+
});
|
|
429
|
+
const cacheStore = getCacheStore(cache, "mx");
|
|
430
|
+
const cached = await cacheStore.get(domain);
|
|
431
|
+
if (cached !== null && cached !== void 0) {
|
|
432
|
+
log(`[resolveMxRecords] Cache hit for ${domain}: ${cached === null || cached === void 0 ? void 0 : cached.length} MX records`);
|
|
433
|
+
return cached;
|
|
434
|
+
}
|
|
435
|
+
log(`[resolveMxRecords] Performing DNS MX lookup for ${domain}`);
|
|
436
|
+
try {
|
|
437
|
+
const records = await node_dns.promises.resolveMx(domain);
|
|
438
|
+
records === null || records === void 0 ? void 0 : records.sort((a, b) => {
|
|
439
|
+
if (a.priority < b.priority) {
|
|
440
|
+
return -1;
|
|
441
|
+
}
|
|
442
|
+
if (a.priority > b.priority) {
|
|
443
|
+
return 1;
|
|
444
|
+
}
|
|
445
|
+
return 0;
|
|
446
|
+
});
|
|
447
|
+
const exchanges = records === null || records === void 0 ? void 0 : records.map((record) => record.exchange);
|
|
448
|
+
log(`[resolveMxRecords] Found ${exchanges === null || exchanges === void 0 ? void 0 : exchanges.length} MX records for ${domain}: [${exchanges === null || exchanges === void 0 ? void 0 : exchanges.join(", ")}]`);
|
|
449
|
+
await cacheStore.set(domain, exchanges);
|
|
450
|
+
log(`[resolveMxRecords] Cached ${exchanges === null || exchanges === void 0 ? void 0 : exchanges.length} MX records for ${domain}`);
|
|
451
|
+
return exchanges;
|
|
452
|
+
} catch (error) {
|
|
453
|
+
log(`[resolveMxRecords] MX lookup failed for ${domain}, caching empty result`);
|
|
454
|
+
await cacheStore.set(domain, []);
|
|
455
|
+
throw error;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const nameSeparator = [".", "_", "-"];
|
|
460
|
+
const commonNameSuffixes = [
|
|
404
461
|
"mail",
|
|
405
462
|
"email",
|
|
406
463
|
"contact",
|
|
@@ -415,7 +472,7 @@ const COMMON_NAME_SUFFIXES = [
|
|
|
415
472
|
"notifications",
|
|
416
473
|
"alerts"
|
|
417
474
|
];
|
|
418
|
-
const
|
|
475
|
+
const contextualSuffixes = [
|
|
419
476
|
"dev",
|
|
420
477
|
"company",
|
|
421
478
|
"team",
|
|
@@ -447,7 +504,7 @@ const COMMON_TITLES = [
|
|
|
447
504
|
"father",
|
|
448
505
|
"sister"
|
|
449
506
|
];
|
|
450
|
-
const
|
|
507
|
+
const commonFirstName = /* @__PURE__ */ new Set([
|
|
451
508
|
// English names
|
|
452
509
|
"james",
|
|
453
510
|
"john",
|
|
@@ -625,7 +682,7 @@ const COMMON_FIRST_NAMES = /* @__PURE__ */ new Set([
|
|
|
625
682
|
"sekou",
|
|
626
683
|
"mariama"
|
|
627
684
|
]);
|
|
628
|
-
const
|
|
685
|
+
const commonLastName = /* @__PURE__ */ new Set([
|
|
629
686
|
// English surnames
|
|
630
687
|
"smith",
|
|
631
688
|
"johnson",
|
|
@@ -745,19 +802,19 @@ function isYearLike(str) {
|
|
|
745
802
|
return /^(19|20)\d{2}$/.test(str);
|
|
746
803
|
}
|
|
747
804
|
function isKnownFirstName(str) {
|
|
748
|
-
return
|
|
805
|
+
return commonFirstName.has(str.toLowerCase());
|
|
749
806
|
}
|
|
750
807
|
function isKnownLastName(str) {
|
|
751
|
-
return
|
|
808
|
+
return commonLastName.has(str.toLowerCase());
|
|
752
809
|
}
|
|
753
810
|
function isTitle(str) {
|
|
754
811
|
return COMMON_TITLES.includes(str.toLowerCase().replace(".", ""));
|
|
755
812
|
}
|
|
756
813
|
function getFirstNameScore(str) {
|
|
757
814
|
const lower = str.toLowerCase();
|
|
758
|
-
if (
|
|
815
|
+
if (commonFirstName.has(lower))
|
|
759
816
|
return 1;
|
|
760
|
-
if (
|
|
817
|
+
if (commonLastName.has(lower))
|
|
761
818
|
return 0.3;
|
|
762
819
|
if (str.length >= 2 && str.length <= 15 && /^[a-zA-Z]+$/.test(str))
|
|
763
820
|
return 0.5;
|
|
@@ -765,9 +822,9 @@ function getFirstNameScore(str) {
|
|
|
765
822
|
}
|
|
766
823
|
function getLastNameScore(str) {
|
|
767
824
|
const lower = str.toLowerCase();
|
|
768
|
-
if (
|
|
825
|
+
if (commonLastName.has(lower))
|
|
769
826
|
return 1;
|
|
770
|
-
if (
|
|
827
|
+
if (commonFirstName.has(lower))
|
|
771
828
|
return 0.3;
|
|
772
829
|
if (str.length >= 2 && str.length <= 20 && /^[a-zA-Z]+$/.test(str))
|
|
773
830
|
return 0.5;
|
|
@@ -876,7 +933,7 @@ function isLikelyName(str, allowNumbers = false, allowSingleLetter = false) {
|
|
|
876
933
|
return false;
|
|
877
934
|
if (str.length === 1 && allowSingleLetter && /^[a-zA-Z]$/.test(str))
|
|
878
935
|
return true;
|
|
879
|
-
if (
|
|
936
|
+
if (commonNameSuffixes.includes(str.toLowerCase()))
|
|
880
937
|
return false;
|
|
881
938
|
if (allowNumbers) {
|
|
882
939
|
if (/^\d+$/.test(str))
|
|
@@ -938,7 +995,7 @@ function defaultNameDetectionMethod(email) {
|
|
|
938
995
|
}
|
|
939
996
|
}
|
|
940
997
|
if (!firstName && !lastName) {
|
|
941
|
-
for (const separator of
|
|
998
|
+
for (const separator of nameSeparator) {
|
|
942
999
|
if (cleanedLocal.includes(separator)) {
|
|
943
1000
|
const parts = cleanedLocal.split(separator).filter((p) => p.length > 0);
|
|
944
1001
|
if (parts.length === 2) {
|
|
@@ -1000,7 +1057,7 @@ function defaultNameDetectionMethod(email) {
|
|
|
1000
1057
|
const firstParsed = parseCompositeNamePart(first);
|
|
1001
1058
|
const middleParsed = parseCompositeNamePart(middle);
|
|
1002
1059
|
const lastParsed = parseCompositeNamePart(last);
|
|
1003
|
-
const isLastSuffix =
|
|
1060
|
+
const isLastSuffix = commonNameSuffixes.includes(last.toLowerCase()) || contextualSuffixes.includes(last.toLowerCase()) || isYearLike(last);
|
|
1004
1061
|
if (isLastSuffix) {
|
|
1005
1062
|
if (isLikelyName(first, true, true) && isLikelyName(middle, true, true)) {
|
|
1006
1063
|
const cleanedFirst = firstParsed.hasNumbers ? firstParsed.cleaned : first;
|
|
@@ -1033,7 +1090,7 @@ function defaultNameDetectionMethod(email) {
|
|
|
1033
1090
|
} else if (parts.length > 3) {
|
|
1034
1091
|
const firstPart = parts[0];
|
|
1035
1092
|
const lastPartLower = parts[parts.length - 1].toLowerCase();
|
|
1036
|
-
const isLastPartSuffix =
|
|
1093
|
+
const isLastPartSuffix = commonNameSuffixes.includes(lastPartLower) || contextualSuffixes.includes(lastPartLower) || isYearLike(parts[parts.length - 1]);
|
|
1037
1094
|
const effectiveLastIndex = isLastPartSuffix ? parts.length - 2 : parts.length - 1;
|
|
1038
1095
|
const lastToUse = effectiveLastIndex >= 0 ? parts[effectiveLastIndex] : null;
|
|
1039
1096
|
if (lastToUse && isLikelyName(firstPart, true, true) && isLikelyName(lastToUse, true, true)) {
|
|
@@ -1116,7 +1173,7 @@ function detectNameFromEmail(params) {
|
|
|
1116
1173
|
}
|
|
1117
1174
|
return defaultNameDetectionMethod(email);
|
|
1118
1175
|
}
|
|
1119
|
-
function
|
|
1176
|
+
function cleanNameForAlgorithm(name) {
|
|
1120
1177
|
if (!name)
|
|
1121
1178
|
return "";
|
|
1122
1179
|
let cleaned = name.replace(/[._*]/g, "");
|
|
@@ -1126,13 +1183,13 @@ function cleanNameForAlgrothin(name) {
|
|
|
1126
1183
|
}
|
|
1127
1184
|
return cleaned;
|
|
1128
1185
|
}
|
|
1129
|
-
function
|
|
1186
|
+
function detectNameForAlgorithm(email) {
|
|
1130
1187
|
const detectedName = detectName(email);
|
|
1131
1188
|
if (!detectedName) {
|
|
1132
1189
|
return null;
|
|
1133
1190
|
}
|
|
1134
|
-
const cleanedFirstName = detectedName.firstName ?
|
|
1135
|
-
const cleanedLastName = detectedName.lastName ?
|
|
1191
|
+
const cleanedFirstName = detectedName.firstName ? cleanNameForAlgorithm(detectedName.firstName) : void 0;
|
|
1192
|
+
const cleanedLastName = detectedName.lastName ? cleanNameForAlgorithm(detectedName.lastName) : void 0;
|
|
1136
1193
|
if (!cleanedFirstName && !cleanedLastName) {
|
|
1137
1194
|
return null;
|
|
1138
1195
|
}
|
|
@@ -1147,6 +1204,156 @@ function detectName(email) {
|
|
|
1147
1204
|
return detectNameFromEmail({ email });
|
|
1148
1205
|
}
|
|
1149
1206
|
|
|
1207
|
+
exports.VerificationErrorCode = void 0;
|
|
1208
|
+
(function(VerificationErrorCode2) {
|
|
1209
|
+
VerificationErrorCode2["invalidFormat"] = "INVALID_FORMAT";
|
|
1210
|
+
VerificationErrorCode2["invalidDomain"] = "INVALID_DOMAIN";
|
|
1211
|
+
VerificationErrorCode2["noMxRecords"] = "NO_MX_RECORDS";
|
|
1212
|
+
VerificationErrorCode2["smtpConnectionFailed"] = "SMTP_CONNECTION_FAILED";
|
|
1213
|
+
VerificationErrorCode2["smtpTimeout"] = "SMTP_TIMEOUT";
|
|
1214
|
+
VerificationErrorCode2["mailboxNotFound"] = "MAILBOX_NOT_FOUND";
|
|
1215
|
+
VerificationErrorCode2["mailboxFull"] = "MAILBOX_FULL";
|
|
1216
|
+
VerificationErrorCode2["networkError"] = "NETWORK_ERROR";
|
|
1217
|
+
VerificationErrorCode2["disposableEmail"] = "DISPOSABLE_EMAIL";
|
|
1218
|
+
VerificationErrorCode2["freeEmailProvider"] = "FREE_EMAIL_PROVIDER";
|
|
1219
|
+
})(exports.VerificationErrorCode || (exports.VerificationErrorCode = {}));
|
|
1220
|
+
exports.EmailProvider = void 0;
|
|
1221
|
+
(function(EmailProvider2) {
|
|
1222
|
+
EmailProvider2["gmail"] = "gmail";
|
|
1223
|
+
EmailProvider2["hotmailB2b"] = "hotmail_b2b";
|
|
1224
|
+
EmailProvider2["hotmailB2c"] = "hotmail_b2c";
|
|
1225
|
+
EmailProvider2["proofpoint"] = "proofpoint";
|
|
1226
|
+
EmailProvider2["mimecast"] = "mimecast";
|
|
1227
|
+
EmailProvider2["yahoo"] = "yahoo";
|
|
1228
|
+
EmailProvider2["everythingElse"] = "everything_else";
|
|
1229
|
+
})(exports.EmailProvider || (exports.EmailProvider = {}));
|
|
1230
|
+
function parseSmtpError(errorMessage) {
|
|
1231
|
+
const lowerError = errorMessage.toLowerCase();
|
|
1232
|
+
const networkErrorPatterns = [
|
|
1233
|
+
"etimedout",
|
|
1234
|
+
"econnrefused",
|
|
1235
|
+
"enotfound",
|
|
1236
|
+
"econnreset",
|
|
1237
|
+
"socket hang up",
|
|
1238
|
+
"connection_timeout",
|
|
1239
|
+
"socket_timeout",
|
|
1240
|
+
"connection_error",
|
|
1241
|
+
"connection_closed"
|
|
1242
|
+
];
|
|
1243
|
+
const isNetworkError = networkErrorPatterns.some((pattern) => lowerError.includes(pattern));
|
|
1244
|
+
if (isNetworkError) {
|
|
1245
|
+
return {
|
|
1246
|
+
isDisabled: false,
|
|
1247
|
+
hasFullInbox: false,
|
|
1248
|
+
isInvalid: true,
|
|
1249
|
+
isCatchAll: false
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
const disabledPatterns = [
|
|
1253
|
+
"account disabled",
|
|
1254
|
+
"account is disabled",
|
|
1255
|
+
"user disabled",
|
|
1256
|
+
"user is disabled",
|
|
1257
|
+
"account locked",
|
|
1258
|
+
"account is locked",
|
|
1259
|
+
"user blocked",
|
|
1260
|
+
"user is blocked",
|
|
1261
|
+
"mailbox disabled",
|
|
1262
|
+
"delivery not authorized",
|
|
1263
|
+
"message rejected",
|
|
1264
|
+
"access denied",
|
|
1265
|
+
"permission denied",
|
|
1266
|
+
"recipient unknown",
|
|
1267
|
+
"recipient address rejected",
|
|
1268
|
+
"user unknown",
|
|
1269
|
+
"address unknown",
|
|
1270
|
+
"invalid recipient",
|
|
1271
|
+
"not a valid recipient",
|
|
1272
|
+
"recipient does not exist",
|
|
1273
|
+
"no such user",
|
|
1274
|
+
"user does not exist",
|
|
1275
|
+
"mailbox unavailable",
|
|
1276
|
+
"recipient unavailable",
|
|
1277
|
+
"address rejected",
|
|
1278
|
+
"550",
|
|
1279
|
+
"551",
|
|
1280
|
+
"553",
|
|
1281
|
+
"not_found",
|
|
1282
|
+
"ambiguous"
|
|
1283
|
+
];
|
|
1284
|
+
const fullInboxPatterns = [
|
|
1285
|
+
"mailbox full",
|
|
1286
|
+
"inbox full",
|
|
1287
|
+
"quota exceeded",
|
|
1288
|
+
"over quota",
|
|
1289
|
+
"storage limit exceeded",
|
|
1290
|
+
"message too large",
|
|
1291
|
+
"insufficient storage",
|
|
1292
|
+
"mailbox over quota",
|
|
1293
|
+
"over the quota",
|
|
1294
|
+
"mailbox size limit exceeded",
|
|
1295
|
+
"account over quota",
|
|
1296
|
+
"storage space",
|
|
1297
|
+
"overquota",
|
|
1298
|
+
"452",
|
|
1299
|
+
"552",
|
|
1300
|
+
"over_quota"
|
|
1301
|
+
];
|
|
1302
|
+
const catchAllPatterns = [
|
|
1303
|
+
"accept all mail",
|
|
1304
|
+
"catch-all",
|
|
1305
|
+
"catchall",
|
|
1306
|
+
"wildcard",
|
|
1307
|
+
"accepts any recipient",
|
|
1308
|
+
"recipient address accepted"
|
|
1309
|
+
];
|
|
1310
|
+
const rateLimitPatterns = [
|
|
1311
|
+
"receiving mail at a rate that",
|
|
1312
|
+
"rate limit",
|
|
1313
|
+
"too many messages",
|
|
1314
|
+
"temporarily rejected",
|
|
1315
|
+
"try again later",
|
|
1316
|
+
"greylisted",
|
|
1317
|
+
"greylist",
|
|
1318
|
+
"deferring",
|
|
1319
|
+
"temporarily deferred",
|
|
1320
|
+
"421",
|
|
1321
|
+
"450",
|
|
1322
|
+
"451",
|
|
1323
|
+
"temporary_failure"
|
|
1324
|
+
];
|
|
1325
|
+
const isDisabled = disabledPatterns.some((pattern) => lowerError.includes(pattern)) || lowerError.startsWith("550") || lowerError.startsWith("551") || lowerError.startsWith("553");
|
|
1326
|
+
const hasFullInbox = fullInboxPatterns.some((pattern) => lowerError.includes(pattern)) || lowerError.startsWith("452") || lowerError.startsWith("552");
|
|
1327
|
+
const isCatchAll = catchAllPatterns.some((pattern) => lowerError.includes(pattern));
|
|
1328
|
+
const isInvalid = !isDisabled && !hasFullInbox && !isCatchAll && !rateLimitPatterns.some((pattern) => lowerError.includes(pattern)) && !lowerError.startsWith("421") && !lowerError.startsWith("450") && !lowerError.startsWith("451");
|
|
1329
|
+
return {
|
|
1330
|
+
isDisabled,
|
|
1331
|
+
hasFullInbox,
|
|
1332
|
+
isInvalid,
|
|
1333
|
+
isCatchAll
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
exports.SMTPStep = void 0;
|
|
1337
|
+
(function(SMTPStep2) {
|
|
1338
|
+
SMTPStep2["greeting"] = "GREETING";
|
|
1339
|
+
SMTPStep2["ehlo"] = "EHLO";
|
|
1340
|
+
SMTPStep2["helo"] = "HELO";
|
|
1341
|
+
SMTPStep2["startTls"] = "STARTTLS";
|
|
1342
|
+
SMTPStep2["mailFrom"] = "MAIL_FROM";
|
|
1343
|
+
SMTPStep2["rcptTo"] = "RCPT_TO";
|
|
1344
|
+
SMTPStep2["vrfy"] = "VRFY";
|
|
1345
|
+
SMTPStep2["quit"] = "QUIT";
|
|
1346
|
+
})(exports.SMTPStep || (exports.SMTPStep = {}));
|
|
1347
|
+
|
|
1348
|
+
function isIPAddress(host) {
|
|
1349
|
+
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
1350
|
+
if (ipv4Regex.test(host)) {
|
|
1351
|
+
const octets = host.split(".");
|
|
1352
|
+
return octets.every((octet) => parseInt(octet, 10) <= 255);
|
|
1353
|
+
}
|
|
1354
|
+
const ipv6Regex = /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^(?:[0-9a-fA-F]{1,4}:){1,7}:$|^(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}$|^(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}$|^(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}$|^(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}$|^(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}$|^[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})$|^::1(?::(?::[0-9a-fA-F]{1,4}){1,7})|$|:(?:(?::[0-9a-fA-F]{1,4}){1,7}:)$/;
|
|
1355
|
+
return ipv6Regex.test(host);
|
|
1356
|
+
}
|
|
1150
1357
|
function isOverQuota(smtpReply) {
|
|
1151
1358
|
return Boolean(smtpReply && /(over quota)/gi.test(smtpReply));
|
|
1152
1359
|
}
|
|
@@ -1156,182 +1363,469 @@ function isInvalidMailboxError(smtpReply) {
|
|
|
1156
1363
|
function isMultilineGreet(smtpReply) {
|
|
1157
1364
|
return Boolean(smtpReply && /^(250|220)-/.test(smtpReply));
|
|
1158
1365
|
}
|
|
1366
|
+
const DEFAULT_PORTS = [25, 587, 465];
|
|
1367
|
+
const DEFAULT_TIMEOUT = 3e3;
|
|
1368
|
+
const DEFAULT_MAX_RETRIES = 1;
|
|
1369
|
+
const PORT_CONFIGS = {
|
|
1370
|
+
25: { tls: false, starttls: true },
|
|
1371
|
+
587: { tls: false, starttls: true },
|
|
1372
|
+
465: { tls: true, starttls: false }
|
|
1373
|
+
};
|
|
1159
1374
|
async function verifyMailboxSMTP(params) {
|
|
1160
|
-
const { local, domain, mxRecords = [],
|
|
1161
|
-
const
|
|
1375
|
+
const { local, domain, mxRecords = [], options = {} } = params;
|
|
1376
|
+
const { ports = DEFAULT_PORTS, timeout = DEFAULT_TIMEOUT, maxRetries = DEFAULT_MAX_RETRIES, tls: tlsConfig = true, hostname = "localhost", useVRFY = true, cache, debug = false, sequence } = options;
|
|
1377
|
+
const log = debug ? (...args) => console.log("[SMTP]", ...args) : () => {
|
|
1378
|
+
};
|
|
1379
|
+
const createSmtpResult = (result, port, tlsUsed, mxHost2) => {
|
|
1380
|
+
const reason = result === true ? "valid" : result === null ? "ambiguous" : "not_found";
|
|
1381
|
+
const parsedError = parseSmtpError(reason);
|
|
1382
|
+
return {
|
|
1383
|
+
canConnectSmtp: result !== null,
|
|
1384
|
+
hasFullInbox: parsedError.hasFullInbox,
|
|
1385
|
+
isCatchAll: parsedError.isCatchAll,
|
|
1386
|
+
isDeliverable: result === true,
|
|
1387
|
+
isDisabled: result === false && parsedError.isDisabled,
|
|
1388
|
+
error: result === null ? reason : result === false ? reason : void 0,
|
|
1389
|
+
providerUsed: exports.EmailProvider.everythingElse,
|
|
1390
|
+
checkedAt: Date.now()
|
|
1391
|
+
};
|
|
1162
1392
|
};
|
|
1393
|
+
const createFailureResult = (error) => ({
|
|
1394
|
+
canConnectSmtp: false,
|
|
1395
|
+
hasFullInbox: false,
|
|
1396
|
+
isCatchAll: false,
|
|
1397
|
+
isDeliverable: false,
|
|
1398
|
+
isDisabled: false,
|
|
1399
|
+
error,
|
|
1400
|
+
providerUsed: exports.EmailProvider.everythingElse,
|
|
1401
|
+
checkedAt: Date.now()
|
|
1402
|
+
});
|
|
1163
1403
|
if (!mxRecords || mxRecords.length === 0) {
|
|
1164
|
-
|
|
1404
|
+
log("No MX records found");
|
|
1405
|
+
return {
|
|
1406
|
+
smtpResult: createFailureResult("No MX records found"),
|
|
1407
|
+
cached: false,
|
|
1408
|
+
port: 0,
|
|
1409
|
+
portCached: false
|
|
1410
|
+
};
|
|
1165
1411
|
}
|
|
1166
|
-
const
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
domain
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1412
|
+
const mxHost = mxRecords[0];
|
|
1413
|
+
log(`Verifying ${local}@${domain} via ${mxHost}`);
|
|
1414
|
+
const smtpCacheStore = cache ? getCacheStore(cache, "smtp") : null;
|
|
1415
|
+
if (smtpCacheStore) {
|
|
1416
|
+
let cachedResult;
|
|
1417
|
+
try {
|
|
1418
|
+
cachedResult = await smtpCacheStore.get(`${mxHost}:${local}@${domain}`);
|
|
1419
|
+
if (cachedResult !== void 0 && cachedResult !== null) {
|
|
1420
|
+
log(`Using cached SMTP result: ${cachedResult.isDeliverable}`);
|
|
1421
|
+
return {
|
|
1422
|
+
smtpResult: cachedResult,
|
|
1423
|
+
cached: true,
|
|
1424
|
+
port: 0,
|
|
1425
|
+
portCached: false
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
} catch (ignoredError) {
|
|
1429
|
+
cachedResult = void 0;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
const smtpPortCacheStore = cache ? getCacheStore(cache, "smtpPort") : null;
|
|
1433
|
+
if (smtpPortCacheStore) {
|
|
1434
|
+
let cachedPort;
|
|
1435
|
+
try {
|
|
1436
|
+
cachedPort = await smtpPortCacheStore.get(mxHost);
|
|
1437
|
+
} catch (ignoredError) {
|
|
1438
|
+
cachedPort = null;
|
|
1180
1439
|
}
|
|
1181
|
-
if (
|
|
1182
|
-
|
|
1440
|
+
if (cachedPort) {
|
|
1441
|
+
log(`Using cached port: ${cachedPort}`);
|
|
1442
|
+
const result = await testSMTPConnection({
|
|
1443
|
+
mxHost,
|
|
1444
|
+
port: cachedPort,
|
|
1445
|
+
local,
|
|
1446
|
+
domain,
|
|
1447
|
+
timeout,
|
|
1448
|
+
tlsConfig,
|
|
1449
|
+
hostname,
|
|
1450
|
+
useVRFY,
|
|
1451
|
+
sequence,
|
|
1452
|
+
log
|
|
1453
|
+
});
|
|
1454
|
+
const smtpResult = createSmtpResult(result);
|
|
1455
|
+
if (smtpCacheStore) {
|
|
1456
|
+
try {
|
|
1457
|
+
await smtpCacheStore.set(`${mxHost}:${local}@${domain}`, smtpResult);
|
|
1458
|
+
log(`Cached SMTP result ${result} for ${local}@${domain} via ${mxHost}`);
|
|
1459
|
+
} catch (ignoredError) {
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
return { smtpResult, cached: false, port: cachedPort, portCached: true };
|
|
1183
1463
|
}
|
|
1184
1464
|
}
|
|
1185
|
-
|
|
1465
|
+
for (const port of ports) {
|
|
1466
|
+
log(`Testing port ${port}`);
|
|
1467
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
1468
|
+
if (attempt > 0) {
|
|
1469
|
+
const delay = Math.min(200 * 2 ** (attempt - 1), 800);
|
|
1470
|
+
log(`Retry ${attempt + 1}, waiting ${delay}ms`);
|
|
1471
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1472
|
+
}
|
|
1473
|
+
const result = await testSMTPConnection({
|
|
1474
|
+
mxHost,
|
|
1475
|
+
port,
|
|
1476
|
+
local,
|
|
1477
|
+
domain,
|
|
1478
|
+
timeout,
|
|
1479
|
+
tlsConfig,
|
|
1480
|
+
hostname,
|
|
1481
|
+
useVRFY,
|
|
1482
|
+
sequence,
|
|
1483
|
+
log
|
|
1484
|
+
});
|
|
1485
|
+
const smtpResult = createSmtpResult(result);
|
|
1486
|
+
if (smtpCacheStore) {
|
|
1487
|
+
try {
|
|
1488
|
+
await smtpCacheStore.set(`${mxHost}:${local}@${domain}`, smtpResult);
|
|
1489
|
+
log(`Cached SMTP result ${result} for ${local}@${domain} via ${mxHost}`);
|
|
1490
|
+
} catch (ignoredError) {
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
if (result !== null) {
|
|
1494
|
+
if (smtpPortCacheStore) {
|
|
1495
|
+
try {
|
|
1496
|
+
await smtpPortCacheStore.set(mxHost, port);
|
|
1497
|
+
log(`Cached port ${port} for ${mxHost}`);
|
|
1498
|
+
} catch (ignoredError) {
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
return { smtpResult, cached: false, port, portCached: false };
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
log("All ports failed");
|
|
1506
|
+
return {
|
|
1507
|
+
smtpResult: createFailureResult("All SMTP connection attempts failed"),
|
|
1508
|
+
cached: false,
|
|
1509
|
+
port: 0,
|
|
1510
|
+
portCached: false
|
|
1511
|
+
};
|
|
1186
1512
|
}
|
|
1187
|
-
async function
|
|
1188
|
-
const {
|
|
1513
|
+
async function testSMTPConnection(params) {
|
|
1514
|
+
const { mxHost, port, local, domain, timeout, tlsConfig, hostname, useVRFY, sequence, log } = params;
|
|
1515
|
+
const portConfig = PORT_CONFIGS[port] || { tls: false, starttls: false };
|
|
1516
|
+
const useTLS = tlsConfig !== false && (portConfig.tls || portConfig.starttls);
|
|
1517
|
+
const implicitTLS = portConfig.tls;
|
|
1518
|
+
const defaultSequence = {
|
|
1519
|
+
steps: [exports.SMTPStep.greeting, exports.SMTPStep.ehlo, exports.SMTPStep.mailFrom, exports.SMTPStep.rcptTo]
|
|
1520
|
+
};
|
|
1521
|
+
const activeSequence = sequence || defaultSequence;
|
|
1522
|
+
if (port === 25) {
|
|
1523
|
+
activeSequence.steps = activeSequence.steps.map((step) => step === exports.SMTPStep.ehlo ? exports.SMTPStep.helo : step);
|
|
1524
|
+
}
|
|
1525
|
+
const tlsOptions = {
|
|
1526
|
+
host: mxHost,
|
|
1527
|
+
servername: isIPAddress(mxHost) ? void 0 : mxHost,
|
|
1528
|
+
// Don't set servername for IP addresses
|
|
1529
|
+
rejectUnauthorized: false,
|
|
1530
|
+
minVersion: "TLSv1.2",
|
|
1531
|
+
...typeof tlsConfig === "object" ? tlsConfig : {}
|
|
1532
|
+
};
|
|
1189
1533
|
return new Promise((resolve) => {
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
});
|
|
1195
|
-
let resTimeout = null;
|
|
1534
|
+
let socket;
|
|
1535
|
+
let buffer = "";
|
|
1536
|
+
let isTLS = implicitTLS;
|
|
1537
|
+
let currentStepIndex = 0;
|
|
1196
1538
|
let resolved = false;
|
|
1197
|
-
let
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
return;
|
|
1201
|
-
cleaned = true;
|
|
1202
|
-
if (resTimeout) {
|
|
1203
|
-
clearTimeout(resTimeout);
|
|
1204
|
-
resTimeout = null;
|
|
1205
|
-
}
|
|
1206
|
-
if (socket && !socket.destroyed) {
|
|
1207
|
-
socket.removeAllListeners();
|
|
1208
|
-
socket.destroy();
|
|
1209
|
-
}
|
|
1210
|
-
};
|
|
1211
|
-
const ret = (result) => {
|
|
1539
|
+
let supportsSTARTTLS = false;
|
|
1540
|
+
let supportsVRFY = false;
|
|
1541
|
+
let cleanup = () => {
|
|
1212
1542
|
if (resolved)
|
|
1213
1543
|
return;
|
|
1214
1544
|
resolved = true;
|
|
1215
|
-
|
|
1216
|
-
log("[verifyMailboxSMTP] closing socket");
|
|
1545
|
+
try {
|
|
1217
1546
|
socket === null || socket === void 0 ? void 0 : socket.write("QUIT\r\n");
|
|
1218
|
-
|
|
1547
|
+
} catch {
|
|
1219
1548
|
}
|
|
1549
|
+
setTimeout(() => socket === null || socket === void 0 ? void 0 : socket.destroy(), 100);
|
|
1550
|
+
};
|
|
1551
|
+
const finish = (result, reason) => {
|
|
1552
|
+
if (resolved)
|
|
1553
|
+
return;
|
|
1554
|
+
log(`${port}: ${reason || (result ? "valid" : "invalid")}`);
|
|
1220
1555
|
cleanup();
|
|
1221
1556
|
resolve(result);
|
|
1222
1557
|
};
|
|
1223
|
-
const
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1558
|
+
const sendCommand = (cmd) => {
|
|
1559
|
+
if (resolved)
|
|
1560
|
+
return;
|
|
1561
|
+
log(`\u2192 ${cmd}`);
|
|
1562
|
+
socket === null || socket === void 0 ? void 0 : socket.write(`${cmd}\r
|
|
1563
|
+
`);
|
|
1564
|
+
};
|
|
1565
|
+
const nextStep = () => {
|
|
1566
|
+
currentStepIndex++;
|
|
1567
|
+
if (currentStepIndex >= activeSequence.steps.length) {
|
|
1568
|
+
finish(true, "sequence_complete");
|
|
1569
|
+
return;
|
|
1231
1570
|
}
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1571
|
+
executeStep(activeSequence.steps[currentStepIndex]);
|
|
1572
|
+
};
|
|
1573
|
+
const executeStep = (step) => {
|
|
1574
|
+
if (resolved)
|
|
1575
|
+
return;
|
|
1576
|
+
switch (step) {
|
|
1577
|
+
case exports.SMTPStep.ehlo:
|
|
1578
|
+
sendCommand(`EHLO ${hostname}`);
|
|
1579
|
+
break;
|
|
1580
|
+
case exports.SMTPStep.helo:
|
|
1581
|
+
sendCommand(`HELO ${domain}`);
|
|
1582
|
+
break;
|
|
1583
|
+
case exports.SMTPStep.greeting:
|
|
1584
|
+
break;
|
|
1585
|
+
case exports.SMTPStep.startTls:
|
|
1586
|
+
sendCommand("STARTTLS");
|
|
1587
|
+
break;
|
|
1588
|
+
case exports.SMTPStep.mailFrom: {
|
|
1589
|
+
const from = activeSequence.from || "<>";
|
|
1590
|
+
sendCommand(`MAIL FROM:${from}`);
|
|
1591
|
+
break;
|
|
1592
|
+
}
|
|
1593
|
+
case exports.SMTPStep.rcptTo:
|
|
1594
|
+
sendCommand(`RCPT TO:<${local}@${domain}>`);
|
|
1595
|
+
break;
|
|
1596
|
+
case exports.SMTPStep.vrfy: {
|
|
1597
|
+
const vrfyTarget = activeSequence.vrfyTarget || local;
|
|
1598
|
+
sendCommand(`VRFY ${vrfyTarget}`);
|
|
1599
|
+
break;
|
|
1600
|
+
}
|
|
1601
|
+
case exports.SMTPStep.quit:
|
|
1602
|
+
sendCommand("QUIT");
|
|
1603
|
+
break;
|
|
1235
1604
|
}
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1605
|
+
};
|
|
1606
|
+
const processResponse = (response) => {
|
|
1607
|
+
if (resolved)
|
|
1608
|
+
return;
|
|
1609
|
+
const code = response.substring(0, 3);
|
|
1610
|
+
const isMultiline = response.length > 3 && response[3] === "-";
|
|
1611
|
+
log(`\u2190 ${response}`);
|
|
1612
|
+
if (isMultilineGreet(response)) {
|
|
1613
|
+
return;
|
|
1239
1614
|
}
|
|
1240
|
-
if (
|
|
1615
|
+
if (isOverQuota(response)) {
|
|
1616
|
+
finish(false, "over_quota");
|
|
1241
1617
|
return;
|
|
1242
|
-
if (messages.length > 0) {
|
|
1243
|
-
const message = messages.shift();
|
|
1244
|
-
log("[verifyMailboxSMTP] writing message", message);
|
|
1245
|
-
return socket.write(`${message}\r
|
|
1246
|
-
`);
|
|
1247
1618
|
}
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1619
|
+
if (isInvalidMailboxError(response)) {
|
|
1620
|
+
finish(false, "not_found");
|
|
1621
|
+
return;
|
|
1622
|
+
}
|
|
1623
|
+
if (isMultiline) {
|
|
1624
|
+
const currentStep2 = activeSequence.steps[currentStepIndex];
|
|
1625
|
+
if (currentStep2 === exports.SMTPStep.ehlo && code === "250") {
|
|
1626
|
+
const upper = response.toUpperCase();
|
|
1627
|
+
if (upper.includes("STARTTLS"))
|
|
1628
|
+
supportsSTARTTLS = true;
|
|
1629
|
+
if (upper.includes("VRFY"))
|
|
1630
|
+
supportsVRFY = true;
|
|
1631
|
+
}
|
|
1632
|
+
if (currentStep2 === exports.SMTPStep.helo && code === "250") {
|
|
1633
|
+
const upper = response.toUpperCase();
|
|
1634
|
+
if (upper.includes("VRFY"))
|
|
1635
|
+
supportsVRFY = true;
|
|
1636
|
+
}
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
if (!response.includes("220") && !response.includes("250") && !response.includes("550") && !response.includes("552")) {
|
|
1640
|
+
finish(null, "unrecognized_response");
|
|
1641
|
+
return;
|
|
1642
|
+
}
|
|
1643
|
+
const currentStep = activeSequence.steps[currentStepIndex];
|
|
1644
|
+
switch (currentStep) {
|
|
1645
|
+
case exports.SMTPStep.greeting:
|
|
1646
|
+
if (code.startsWith("220")) {
|
|
1647
|
+
nextStep();
|
|
1648
|
+
} else {
|
|
1649
|
+
finish(null, "no_greeting");
|
|
1650
|
+
}
|
|
1651
|
+
break;
|
|
1652
|
+
case exports.SMTPStep.ehlo:
|
|
1653
|
+
if (code.startsWith("250")) {
|
|
1654
|
+
const hasSTARTTLS = activeSequence.steps.includes(exports.SMTPStep.startTls);
|
|
1655
|
+
if (!isTLS && useTLS && supportsSTARTTLS && !implicitTLS && hasSTARTTLS) {
|
|
1656
|
+
currentStepIndex = activeSequence.steps.indexOf(exports.SMTPStep.startTls);
|
|
1657
|
+
executeStep(exports.SMTPStep.startTls);
|
|
1658
|
+
} else {
|
|
1659
|
+
nextStep();
|
|
1660
|
+
}
|
|
1661
|
+
} else {
|
|
1662
|
+
finish(null, "ehlo_failed");
|
|
1663
|
+
}
|
|
1664
|
+
break;
|
|
1665
|
+
case exports.SMTPStep.helo:
|
|
1666
|
+
if (code.startsWith("250")) {
|
|
1667
|
+
nextStep();
|
|
1668
|
+
} else {
|
|
1669
|
+
finish(null, "helo_failed");
|
|
1670
|
+
}
|
|
1671
|
+
break;
|
|
1672
|
+
case exports.SMTPStep.startTls:
|
|
1673
|
+
if (code.startsWith("220")) {
|
|
1674
|
+
const plainSocket = socket;
|
|
1675
|
+
socket = tls__namespace.connect({
|
|
1676
|
+
...tlsOptions,
|
|
1677
|
+
socket: plainSocket,
|
|
1678
|
+
servername: isIPAddress(mxHost) ? void 0 : mxHost
|
|
1679
|
+
}, () => {
|
|
1680
|
+
isTLS = true;
|
|
1681
|
+
log("TLS upgraded");
|
|
1682
|
+
buffer = "";
|
|
1683
|
+
const starttlsIndex = activeSequence.steps.indexOf(exports.SMTPStep.startTls);
|
|
1684
|
+
currentStepIndex = starttlsIndex;
|
|
1685
|
+
nextStep();
|
|
1686
|
+
});
|
|
1687
|
+
socket.on("data", handleData);
|
|
1688
|
+
socket.on("error", () => finish(null, "tls_error"));
|
|
1689
|
+
} else {
|
|
1690
|
+
nextStep();
|
|
1691
|
+
}
|
|
1692
|
+
break;
|
|
1693
|
+
case exports.SMTPStep.mailFrom:
|
|
1694
|
+
if (code.startsWith("250")) {
|
|
1695
|
+
nextStep();
|
|
1696
|
+
} else {
|
|
1697
|
+
finish(null, "mail_from_rejected");
|
|
1698
|
+
}
|
|
1699
|
+
break;
|
|
1700
|
+
case exports.SMTPStep.rcptTo:
|
|
1701
|
+
if (code.startsWith("250") || code.startsWith("251")) {
|
|
1702
|
+
finish(true, "valid");
|
|
1703
|
+
} else if (code.startsWith("552") || code.startsWith("452")) {
|
|
1704
|
+
finish(false, "over_quota");
|
|
1705
|
+
} else if (code.startsWith("4")) {
|
|
1706
|
+
finish(null, "temporary_failure");
|
|
1707
|
+
} else if (useVRFY && supportsVRFY && code.startsWith("5") && activeSequence.steps.includes(exports.SMTPStep.vrfy)) {
|
|
1708
|
+
currentStepIndex = activeSequence.steps.indexOf(exports.SMTPStep.vrfy);
|
|
1709
|
+
executeStep(exports.SMTPStep.vrfy);
|
|
1710
|
+
} else {
|
|
1711
|
+
finish(null, "ambiguous");
|
|
1712
|
+
}
|
|
1713
|
+
break;
|
|
1714
|
+
case exports.SMTPStep.vrfy:
|
|
1715
|
+
if (code.startsWith("250") || code.startsWith("252")) {
|
|
1716
|
+
finish(true, "vrfy_valid");
|
|
1717
|
+
} else if (code.startsWith("550")) {
|
|
1718
|
+
finish(false, "vrfy_invalid");
|
|
1719
|
+
} else {
|
|
1720
|
+
finish(null, "vrfy_failed");
|
|
1721
|
+
}
|
|
1722
|
+
break;
|
|
1723
|
+
case exports.SMTPStep.quit:
|
|
1724
|
+
if (code.startsWith("221")) {
|
|
1725
|
+
finish(null, "quit_received");
|
|
1726
|
+
}
|
|
1727
|
+
break;
|
|
1728
|
+
}
|
|
1729
|
+
};
|
|
1730
|
+
let handleData = (data) => {
|
|
1731
|
+
if (resolved)
|
|
1732
|
+
return;
|
|
1733
|
+
buffer += data.toString();
|
|
1734
|
+
let pos;
|
|
1735
|
+
while ((pos = buffer.indexOf("\r\n")) !== -1) {
|
|
1736
|
+
const line = buffer.substring(0, pos);
|
|
1737
|
+
buffer = buffer.substring(pos + 2);
|
|
1738
|
+
processResponse(line.trim());
|
|
1739
|
+
}
|
|
1740
|
+
};
|
|
1741
|
+
if (port < 0 || port > 65535 || !Number.isInteger(port)) {
|
|
1742
|
+
finish(null, "invalid_port");
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
if (implicitTLS) {
|
|
1746
|
+
const connectOptions = {
|
|
1747
|
+
...tlsOptions,
|
|
1748
|
+
port,
|
|
1749
|
+
servername: isIPAddress(mxHost) ? void 0 : mxHost
|
|
1750
|
+
};
|
|
1751
|
+
socket = tls__namespace.connect(connectOptions, () => {
|
|
1752
|
+
log(`Connected to ${mxHost}:${port} with TLS`);
|
|
1753
|
+
socket.on("data", handleData);
|
|
1754
|
+
});
|
|
1755
|
+
} else {
|
|
1756
|
+
socket = net__namespace.connect({ host: mxHost, port }, () => {
|
|
1757
|
+
log(`Connected to ${mxHost}:${port}`);
|
|
1758
|
+
socket.on("data", handleData);
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1761
|
+
if (activeSequence.steps.length === 0) {
|
|
1762
|
+
finish(true, "sequence_complete");
|
|
1763
|
+
return;
|
|
1764
|
+
}
|
|
1765
|
+
const firstStep = activeSequence.steps[0];
|
|
1766
|
+
let connectionTimeout;
|
|
1767
|
+
let stepTimeout;
|
|
1768
|
+
const resetActivityTimeout = () => {
|
|
1769
|
+
if (stepTimeout) {
|
|
1770
|
+
clearTimeout(stepTimeout);
|
|
1771
|
+
}
|
|
1772
|
+
stepTimeout = setTimeout(() => {
|
|
1773
|
+
if (!resolved) {
|
|
1774
|
+
log(`Step timeout after ${timeout}ms of inactivity`);
|
|
1775
|
+
finish(null, "step_timeout");
|
|
1776
|
+
}
|
|
1777
|
+
}, timeout);
|
|
1778
|
+
};
|
|
1779
|
+
connectionTimeout = setTimeout(() => {
|
|
1780
|
+
if (!resolved) {
|
|
1781
|
+
log(`Connection timeout after ${timeout}ms`);
|
|
1782
|
+
finish(null, "connection_timeout");
|
|
1783
|
+
}
|
|
1784
|
+
}, timeout);
|
|
1785
|
+
if (firstStep !== exports.SMTPStep.greeting) {
|
|
1786
|
+
executeStep(firstStep);
|
|
1787
|
+
}
|
|
1788
|
+
socket.setTimeout(timeout, () => {
|
|
1255
1789
|
if (!resolved) {
|
|
1256
|
-
log(
|
|
1257
|
-
|
|
1790
|
+
log(`Socket timeout after ${timeout}ms`);
|
|
1791
|
+
finish(null, "socket_timeout");
|
|
1258
1792
|
}
|
|
1259
1793
|
});
|
|
1260
|
-
socket.on("
|
|
1261
|
-
log(
|
|
1262
|
-
|
|
1794
|
+
socket.on("error", (error) => {
|
|
1795
|
+
log(`Socket error: ${error.message}`);
|
|
1796
|
+
if (!resolved)
|
|
1797
|
+
finish(null, "connection_error");
|
|
1263
1798
|
});
|
|
1264
|
-
|
|
1265
|
-
log(`[verifyMailboxSMTP] timed out (${timeout} ms)`);
|
|
1799
|
+
socket.on("close", () => {
|
|
1266
1800
|
if (!resolved) {
|
|
1267
|
-
|
|
1268
|
-
|
|
1801
|
+
log("Socket closed unexpectedly");
|
|
1802
|
+
finish(null, "connection_closed");
|
|
1269
1803
|
}
|
|
1270
|
-
}
|
|
1804
|
+
});
|
|
1805
|
+
const originalHandleData = handleData;
|
|
1806
|
+
handleData = (data) => {
|
|
1807
|
+
resetActivityTimeout();
|
|
1808
|
+
originalHandleData(data);
|
|
1809
|
+
};
|
|
1810
|
+
const enhancedCleanup = () => {
|
|
1811
|
+
if (resolved)
|
|
1812
|
+
return;
|
|
1813
|
+
resolved = true;
|
|
1814
|
+
if (connectionTimeout)
|
|
1815
|
+
clearTimeout(connectionTimeout);
|
|
1816
|
+
if (stepTimeout)
|
|
1817
|
+
clearTimeout(stepTimeout);
|
|
1818
|
+
socket.setTimeout(0);
|
|
1819
|
+
try {
|
|
1820
|
+
socket === null || socket === void 0 ? void 0 : socket.write("QUIT\r\n");
|
|
1821
|
+
} catch {
|
|
1822
|
+
}
|
|
1823
|
+
setTimeout(() => socket === null || socket === void 0 ? void 0 : socket.destroy(), 100);
|
|
1824
|
+
};
|
|
1825
|
+
cleanup = enhancedCleanup;
|
|
1271
1826
|
});
|
|
1272
1827
|
}
|
|
1273
1828
|
|
|
1274
|
-
exports.VerificationErrorCode = void 0;
|
|
1275
|
-
(function(VerificationErrorCode2) {
|
|
1276
|
-
VerificationErrorCode2["INVALID_FORMAT"] = "INVALID_FORMAT";
|
|
1277
|
-
VerificationErrorCode2["INVALID_DOMAIN"] = "INVALID_DOMAIN";
|
|
1278
|
-
VerificationErrorCode2["NO_MX_RECORDS"] = "NO_MX_RECORDS";
|
|
1279
|
-
VerificationErrorCode2["SMTP_CONNECTION_FAILED"] = "SMTP_CONNECTION_FAILED";
|
|
1280
|
-
VerificationErrorCode2["SMTP_TIMEOUT"] = "SMTP_TIMEOUT";
|
|
1281
|
-
VerificationErrorCode2["MAILBOX_NOT_FOUND"] = "MAILBOX_NOT_FOUND";
|
|
1282
|
-
VerificationErrorCode2["MAILBOX_FULL"] = "MAILBOX_FULL";
|
|
1283
|
-
VerificationErrorCode2["NETWORK_ERROR"] = "NETWORK_ERROR";
|
|
1284
|
-
VerificationErrorCode2["DISPOSABLE_EMAIL"] = "DISPOSABLE_EMAIL";
|
|
1285
|
-
VerificationErrorCode2["FREE_EMAIL_PROVIDER"] = "FREE_EMAIL_PROVIDER";
|
|
1286
|
-
})(exports.VerificationErrorCode || (exports.VerificationErrorCode = {}));
|
|
1287
|
-
|
|
1288
|
-
async function isValidEmailDomain(emailOrDomain, cache) {
|
|
1289
|
-
let [_, emailDomain] = (emailOrDomain === null || emailOrDomain === void 0 ? void 0 : emailOrDomain.split("@")) || [];
|
|
1290
|
-
if (!emailDomain) {
|
|
1291
|
-
emailDomain = _;
|
|
1292
|
-
}
|
|
1293
|
-
if (!emailDomain) {
|
|
1294
|
-
return false;
|
|
1295
|
-
}
|
|
1296
|
-
const cacheStore = getCacheStore(cache, "domainValid");
|
|
1297
|
-
const cached = await cacheStore.get(emailDomain);
|
|
1298
|
-
if (cached !== null && cached !== void 0) {
|
|
1299
|
-
return cached;
|
|
1300
|
-
}
|
|
1301
|
-
try {
|
|
1302
|
-
const result = psl.isValid(emailDomain) || false;
|
|
1303
|
-
await cacheStore.set(emailDomain, result);
|
|
1304
|
-
return result;
|
|
1305
|
-
} catch (_e) {
|
|
1306
|
-
await cacheStore.set(emailDomain, false);
|
|
1307
|
-
return false;
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
function isValidEmail(emailAddress) {
|
|
1311
|
-
if (!emailAddress || typeof emailAddress !== "string") {
|
|
1312
|
-
return false;
|
|
1313
|
-
}
|
|
1314
|
-
const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
|
1315
|
-
const emailLower = emailAddress.toLowerCase();
|
|
1316
|
-
if (emailLower.indexOf(".+") !== -1)
|
|
1317
|
-
return false;
|
|
1318
|
-
if (emailLower.indexOf("..") !== -1)
|
|
1319
|
-
return false;
|
|
1320
|
-
if (emailLower.startsWith(".") || emailLower.endsWith("."))
|
|
1321
|
-
return false;
|
|
1322
|
-
const parts = emailAddress.split("@");
|
|
1323
|
-
if (parts.length !== 2)
|
|
1324
|
-
return false;
|
|
1325
|
-
const [localPart, domain] = parts;
|
|
1326
|
-
if (!localPart || !domain)
|
|
1327
|
-
return false;
|
|
1328
|
-
if (localPart.length > 64)
|
|
1329
|
-
return false;
|
|
1330
|
-
if (domain.length > 253)
|
|
1331
|
-
return false;
|
|
1332
|
-
return re.test(emailLower);
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
1829
|
const defaultRegex = {
|
|
1336
1830
|
domainName: "Domain Name: *([^\\s]+)",
|
|
1337
1831
|
registrar: "Registrar: *(.+)",
|
|
@@ -1749,7 +2243,7 @@ function parseWhoisData({ rawData, domain }) {
|
|
|
1749
2243
|
return result;
|
|
1750
2244
|
}
|
|
1751
2245
|
|
|
1752
|
-
const
|
|
2246
|
+
const whoisServers = {
|
|
1753
2247
|
com: "whois.verisign-grs.com",
|
|
1754
2248
|
net: "whois.verisign-grs.com",
|
|
1755
2249
|
org: "whois.pir.org",
|
|
@@ -1849,7 +2343,7 @@ async function getWhoisData(domain, timeout = 5e3, debug = false, cache) {
|
|
|
1849
2343
|
throw new Error("Invalid domain");
|
|
1850
2344
|
}
|
|
1851
2345
|
log(`[whois] extracted TLD: ${tld} for domain: ${domain}`);
|
|
1852
|
-
const whoisServer =
|
|
2346
|
+
const whoisServer = whoisServers[tld];
|
|
1853
2347
|
if (!whoisServer) {
|
|
1854
2348
|
log(`[whois] no specific server for TLD ${tld}, trying IANA`);
|
|
1855
2349
|
const defaultServer = "whois.iana.org";
|
|
@@ -1875,8 +2369,8 @@ async function getWhoisData(domain, timeout = 5e3, debug = false, cache) {
|
|
|
1875
2369
|
await cacheStore.set(cacheKey, whoisData);
|
|
1876
2370
|
log(`[whois] successfully retrieved and cached WHOIS data for ${domain}`);
|
|
1877
2371
|
return whoisData;
|
|
1878
|
-
} catch (
|
|
1879
|
-
log(`[whois] failed to get WHOIS data for ${domain}: ${
|
|
2372
|
+
} catch (ignoredError) {
|
|
2373
|
+
log(`[whois] failed to get WHOIS data for ${domain}: ${ignoredError instanceof Error ? ignoredError.message : "Unknown error"}`);
|
|
1880
2374
|
return null;
|
|
1881
2375
|
}
|
|
1882
2376
|
}
|
|
@@ -1913,8 +2407,8 @@ async function getDomainAge(domain, timeout = 5e3, debug = false, cache) {
|
|
|
1913
2407
|
expirationDate: whoisData.expirationDate ? new Date(whoisData.expirationDate) : null,
|
|
1914
2408
|
updatedDate: whoisData.updatedDate ? new Date(whoisData.updatedDate) : null
|
|
1915
2409
|
};
|
|
1916
|
-
} catch (
|
|
1917
|
-
log(`[whois] error getting domain age for ${domain}: ${
|
|
2410
|
+
} catch (ignoredError) {
|
|
2411
|
+
log(`[whois] error getting domain age for ${domain}: ${ignoredError instanceof Error ? ignoredError.message : "Unknown error"}`);
|
|
1918
2412
|
return null;
|
|
1919
2413
|
}
|
|
1920
2414
|
}
|
|
@@ -1985,8 +2479,8 @@ async function getDomainRegistrationStatus(domain, timeout = 5e3, debug = false,
|
|
|
1985
2479
|
isPendingDelete,
|
|
1986
2480
|
isLocked
|
|
1987
2481
|
};
|
|
1988
|
-
} catch (
|
|
1989
|
-
log(`[whois] error getting domain registration status for ${domain}: ${
|
|
2482
|
+
} catch (ignoredError) {
|
|
2483
|
+
log(`[whois] error getting domain registration status for ${domain}: ${ignoredError instanceof Error ? ignoredError.message : "Unknown error"}`);
|
|
1990
2484
|
return null;
|
|
1991
2485
|
}
|
|
1992
2486
|
}
|
|
@@ -2162,7 +2656,7 @@ function createErrorResult(email, _error) {
|
|
|
2162
2656
|
metadata: {
|
|
2163
2657
|
verificationTime: 0,
|
|
2164
2658
|
cached: false,
|
|
2165
|
-
error: exports.VerificationErrorCode.
|
|
2659
|
+
error: exports.VerificationErrorCode.smtpConnectionFailed
|
|
2166
2660
|
}
|
|
2167
2661
|
};
|
|
2168
2662
|
}
|
|
@@ -2182,25 +2676,31 @@ async function isDisposableEmail(params) {
|
|
|
2182
2676
|
let cached;
|
|
2183
2677
|
try {
|
|
2184
2678
|
cached = await cacheStore.get(emailDomain);
|
|
2185
|
-
} catch (
|
|
2679
|
+
} catch (ignoredError) {
|
|
2186
2680
|
cached = null;
|
|
2187
2681
|
}
|
|
2188
2682
|
if (cached !== null && cached !== void 0) {
|
|
2189
|
-
log(`[isDisposableEmail] Cache hit for ${emailDomain}: ${cached}`);
|
|
2190
|
-
return cached;
|
|
2683
|
+
log(`[isDisposableEmail] Cache hit for ${emailDomain}: ${cached.isDisposable}`);
|
|
2684
|
+
return cached.isDisposable;
|
|
2191
2685
|
}
|
|
2192
2686
|
if (!disposableEmailProviders) {
|
|
2193
2687
|
disposableEmailProviders = new Set(require("./disposable-email-providers.json"));
|
|
2194
2688
|
}
|
|
2195
|
-
const
|
|
2689
|
+
const isDisposable = disposableEmailProviders.has(emailDomain);
|
|
2690
|
+
const richResult = {
|
|
2691
|
+
isDisposable,
|
|
2692
|
+
source: "disposable-email-providers.json",
|
|
2693
|
+
category: isDisposable ? "disposable" : void 0,
|
|
2694
|
+
checkedAt: Date.now()
|
|
2695
|
+
};
|
|
2196
2696
|
try {
|
|
2197
|
-
await cacheStore.set(emailDomain,
|
|
2198
|
-
log(`[isDisposableEmail] Cached result for ${emailDomain}: ${
|
|
2199
|
-
} catch (
|
|
2697
|
+
await cacheStore.set(emailDomain, richResult);
|
|
2698
|
+
log(`[isDisposableEmail] Cached result for ${emailDomain}: ${isDisposable}`);
|
|
2699
|
+
} catch (ignoredError) {
|
|
2200
2700
|
log(`[isDisposableEmail] Cache write error for ${emailDomain}`);
|
|
2201
2701
|
}
|
|
2202
|
-
log(`[isDisposableEmail] Check result for ${emailDomain}: ${
|
|
2203
|
-
return
|
|
2702
|
+
log(`[isDisposableEmail] Check result for ${emailDomain}: ${isDisposable}`);
|
|
2703
|
+
return isDisposable;
|
|
2204
2704
|
}
|
|
2205
2705
|
async function isFreeEmail(params) {
|
|
2206
2706
|
const { emailOrDomain, cache, logger } = params;
|
|
@@ -2215,25 +2715,30 @@ async function isFreeEmail(params) {
|
|
|
2215
2715
|
let cached;
|
|
2216
2716
|
try {
|
|
2217
2717
|
cached = await cacheStore.get(emailDomain);
|
|
2218
|
-
} catch (
|
|
2718
|
+
} catch (ignoredError) {
|
|
2219
2719
|
cached = null;
|
|
2220
2720
|
}
|
|
2221
2721
|
if (cached !== null && cached !== void 0) {
|
|
2222
|
-
log(`[isFreeEmail] Cache hit for ${emailDomain}: ${cached}`);
|
|
2223
|
-
return cached;
|
|
2722
|
+
log(`[isFreeEmail] Cache hit for ${emailDomain}: ${cached.isFree}`);
|
|
2723
|
+
return cached.isFree;
|
|
2224
2724
|
}
|
|
2225
2725
|
if (!freeEmailProviders) {
|
|
2226
2726
|
freeEmailProviders = new Set(require("./free-email-providers.json"));
|
|
2227
2727
|
}
|
|
2228
|
-
const
|
|
2728
|
+
const isFree = freeEmailProviders.has(emailDomain);
|
|
2729
|
+
const richResult = {
|
|
2730
|
+
isFree,
|
|
2731
|
+
provider: isFree ? emailDomain : void 0,
|
|
2732
|
+
checkedAt: Date.now()
|
|
2733
|
+
};
|
|
2229
2734
|
try {
|
|
2230
|
-
await cacheStore.set(emailDomain,
|
|
2231
|
-
log(`[isFreeEmail] Cached result for ${emailDomain}: ${
|
|
2232
|
-
} catch (
|
|
2735
|
+
await cacheStore.set(emailDomain, richResult);
|
|
2736
|
+
log(`[isFreeEmail] Cached result for ${emailDomain}: ${isFree}`);
|
|
2737
|
+
} catch (ignoredError) {
|
|
2233
2738
|
log(`[isFreeEmail] Cache write error for ${emailDomain}`);
|
|
2234
2739
|
}
|
|
2235
|
-
log(`[isFreeEmail] Check result for ${emailDomain}: ${
|
|
2236
|
-
return
|
|
2740
|
+
log(`[isFreeEmail] Check result for ${emailDomain}: ${isFree}`);
|
|
2741
|
+
return isFree;
|
|
2237
2742
|
}
|
|
2238
2743
|
const domainPorts = {
|
|
2239
2744
|
// 465 or 587
|
|
@@ -2241,7 +2746,7 @@ const domainPorts = {
|
|
|
2241
2746
|
"ovh.net": 465
|
|
2242
2747
|
};
|
|
2243
2748
|
async function verifyEmail(params) {
|
|
2244
|
-
var _a;
|
|
2749
|
+
var _a, _b;
|
|
2245
2750
|
const { emailAddress, timeout = 4e3, verifyMx = true, verifySmtp = false, debug = false, checkDisposable = true, checkFree = true, detectName: detectName2 = false, nameDetectionMethod, suggestDomain: suggestDomain2 = true, domainSuggestionMethod, commonDomains, checkDomainAge = false, checkDomainRegistration = false, whoisTimeout = 5e3, skipMxForDisposable = false, skipDomainWhoisForDisposable = false } = params;
|
|
2246
2751
|
const startTime = Date.now();
|
|
2247
2752
|
const log = debug ? console.debug : (..._args) => {
|
|
@@ -2261,7 +2766,7 @@ async function verifyEmail(params) {
|
|
|
2261
2766
|
if (!isValidEmail(emailAddress)) {
|
|
2262
2767
|
if (result.metadata) {
|
|
2263
2768
|
result.metadata.verificationTime = Date.now() - startTime;
|
|
2264
|
-
result.metadata.error = exports.VerificationErrorCode.
|
|
2769
|
+
result.metadata.error = exports.VerificationErrorCode.invalidFormat;
|
|
2265
2770
|
}
|
|
2266
2771
|
return result;
|
|
2267
2772
|
}
|
|
@@ -2287,14 +2792,14 @@ async function verifyEmail(params) {
|
|
|
2287
2792
|
if (!domain || !local) {
|
|
2288
2793
|
if (result.metadata) {
|
|
2289
2794
|
result.metadata.verificationTime = Date.now() - startTime;
|
|
2290
|
-
result.metadata.error = exports.VerificationErrorCode.
|
|
2795
|
+
result.metadata.error = exports.VerificationErrorCode.invalidFormat;
|
|
2291
2796
|
}
|
|
2292
2797
|
return result;
|
|
2293
2798
|
}
|
|
2294
2799
|
if (!await isValidEmailDomain(domain, params.cache)) {
|
|
2295
2800
|
if (result.metadata) {
|
|
2296
2801
|
result.metadata.verificationTime = Date.now() - startTime;
|
|
2297
|
-
result.metadata.error = exports.VerificationErrorCode.
|
|
2802
|
+
result.metadata.error = exports.VerificationErrorCode.invalidDomain;
|
|
2298
2803
|
}
|
|
2299
2804
|
return result;
|
|
2300
2805
|
}
|
|
@@ -2303,7 +2808,7 @@ async function verifyEmail(params) {
|
|
|
2303
2808
|
result.isDisposable = await isDisposableEmail({ emailOrDomain: emailAddress, cache: params.cache, logger: log });
|
|
2304
2809
|
log(`[verifyEmail] Disposable check result: ${result.isDisposable}`);
|
|
2305
2810
|
if (result.isDisposable && result.metadata) {
|
|
2306
|
-
result.metadata.error = exports.VerificationErrorCode.
|
|
2811
|
+
result.metadata.error = exports.VerificationErrorCode.disposableEmail;
|
|
2307
2812
|
}
|
|
2308
2813
|
}
|
|
2309
2814
|
if (checkFree) {
|
|
@@ -2324,8 +2829,8 @@ async function verifyEmail(params) {
|
|
|
2324
2829
|
try {
|
|
2325
2830
|
result.domainAge = await getDomainAge(domain, whoisTimeout, debug, params.cache);
|
|
2326
2831
|
log(`[verifyEmail] Domain age result:`, result.domainAge ? `${result.domainAge.ageInDays} days` : "null");
|
|
2327
|
-
} catch (
|
|
2328
|
-
log("[verifyEmail] Failed to get domain age",
|
|
2832
|
+
} catch (error) {
|
|
2833
|
+
log("[verifyEmail] Failed to get domain age", error);
|
|
2329
2834
|
result.domainAge = null;
|
|
2330
2835
|
}
|
|
2331
2836
|
} else if (checkDomainAge && shouldSkipDomainWhois) {
|
|
@@ -2336,8 +2841,8 @@ async function verifyEmail(params) {
|
|
|
2336
2841
|
try {
|
|
2337
2842
|
result.domainRegistration = await getDomainRegistrationStatus(domain, whoisTimeout, debug, params.cache);
|
|
2338
2843
|
log(`[verifyEmail] Domain registration result:`, ((_a = result.domainRegistration) === null || _a === void 0 ? void 0 : _a.isRegistered) ? "registered" : "not registered");
|
|
2339
|
-
} catch (
|
|
2340
|
-
log("[verifyEmail] Failed to get domain registration status",
|
|
2844
|
+
} catch (error) {
|
|
2845
|
+
log("[verifyEmail] Failed to get domain registration status", error);
|
|
2341
2846
|
result.domainRegistration = null;
|
|
2342
2847
|
}
|
|
2343
2848
|
} else if (checkDomainRegistration && shouldSkipDomainWhois) {
|
|
@@ -2350,14 +2855,14 @@ async function verifyEmail(params) {
|
|
|
2350
2855
|
result.validMx = mxRecords.length > 0;
|
|
2351
2856
|
log(`[verifyEmail] MX records found: ${mxRecords.length}, valid: ${result.validMx}`);
|
|
2352
2857
|
if (!result.validMx && result.metadata) {
|
|
2353
|
-
result.metadata.error = exports.VerificationErrorCode.
|
|
2858
|
+
result.metadata.error = exports.VerificationErrorCode.noMxRecords;
|
|
2354
2859
|
}
|
|
2355
2860
|
if (verifySmtp && mxRecords.length > 0) {
|
|
2356
2861
|
const cacheKey = `${emailAddress}:smtp`;
|
|
2357
2862
|
const smtpCacheInstance = getCacheStore(params.cache, "smtp");
|
|
2358
2863
|
const cachedSmtp = await smtpCacheInstance.get(cacheKey);
|
|
2359
2864
|
if (cachedSmtp !== null && cachedSmtp !== void 0) {
|
|
2360
|
-
result.validSmtp = cachedSmtp;
|
|
2865
|
+
result.validSmtp = (_b = cachedSmtp.isDeliverable) !== null && _b !== void 0 ? _b : null;
|
|
2361
2866
|
log(`[verifyEmail] SMTP result from cache: ${result.validSmtp} for ${emailAddress}`);
|
|
2362
2867
|
if (result.metadata) {
|
|
2363
2868
|
result.metadata.cached = true;
|
|
@@ -2377,30 +2882,39 @@ async function verifyEmail(params) {
|
|
|
2377
2882
|
domainPort = domainPorts[mxDomain.domain];
|
|
2378
2883
|
}
|
|
2379
2884
|
}
|
|
2380
|
-
const smtpResult = await verifyMailboxSMTP({
|
|
2885
|
+
const { smtpResult, cached, port } = await verifyMailboxSMTP({
|
|
2381
2886
|
local,
|
|
2382
2887
|
domain,
|
|
2383
2888
|
mxRecords,
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2889
|
+
options: {
|
|
2890
|
+
cache: params.cache,
|
|
2891
|
+
ports: domainPort ? [domainPort] : void 0,
|
|
2892
|
+
timeout,
|
|
2893
|
+
debug,
|
|
2894
|
+
maxRetries: params.retryAttempts
|
|
2895
|
+
}
|
|
2388
2896
|
});
|
|
2389
2897
|
await smtpCacheInstance.set(cacheKey, smtpResult);
|
|
2390
|
-
|
|
2898
|
+
if (!smtpResult.canConnectSmtp) {
|
|
2899
|
+
result.validSmtp = null;
|
|
2900
|
+
} else {
|
|
2901
|
+
result.validSmtp = smtpResult.isDeliverable;
|
|
2902
|
+
}
|
|
2903
|
+
if (result.metadata)
|
|
2904
|
+
result.metadata.cached = cached;
|
|
2391
2905
|
log(`[verifyEmail] SMTP verification result: ${result.validSmtp} for ${emailAddress} (cached for future use)`);
|
|
2392
2906
|
}
|
|
2393
2907
|
if (result.validSmtp === false && result.metadata) {
|
|
2394
|
-
result.metadata.error = exports.VerificationErrorCode.
|
|
2908
|
+
result.metadata.error = exports.VerificationErrorCode.mailboxNotFound;
|
|
2395
2909
|
} else if (result.validSmtp === null && result.metadata) {
|
|
2396
|
-
result.metadata.error = exports.VerificationErrorCode.
|
|
2910
|
+
result.metadata.error = exports.VerificationErrorCode.smtpConnectionFailed;
|
|
2397
2911
|
}
|
|
2398
2912
|
}
|
|
2399
|
-
} catch (
|
|
2400
|
-
log("[verifyEmail] Failed to resolve MX records",
|
|
2913
|
+
} catch (error) {
|
|
2914
|
+
log("[verifyEmail] Failed to resolve MX records", error);
|
|
2401
2915
|
result.validMx = false;
|
|
2402
2916
|
if (result.metadata) {
|
|
2403
|
-
result.metadata.error = exports.VerificationErrorCode.
|
|
2917
|
+
result.metadata.error = exports.VerificationErrorCode.noMxRecords;
|
|
2404
2918
|
}
|
|
2405
2919
|
}
|
|
2406
2920
|
} else if ((verifyMx || verifySmtp) && shouldSkipMx) {
|
|
@@ -2412,16 +2926,16 @@ async function verifyEmail(params) {
|
|
|
2412
2926
|
return result;
|
|
2413
2927
|
}
|
|
2414
2928
|
|
|
2415
|
-
exports.COMMON_EMAIL_DOMAINS = COMMON_EMAIL_DOMAINS;
|
|
2416
2929
|
exports.DEFAULT_CACHE_OPTIONS = DEFAULT_CACHE_OPTIONS;
|
|
2417
2930
|
exports.LRUAdapter = LRUAdapter;
|
|
2418
2931
|
exports.RedisAdapter = RedisAdapter;
|
|
2419
|
-
exports.
|
|
2932
|
+
exports.cleanNameForAlgorithm = cleanNameForAlgorithm;
|
|
2420
2933
|
exports.clearDefaultCache = clearDefaultCache;
|
|
2934
|
+
exports.commonEmailDomains = commonEmailDomains;
|
|
2421
2935
|
exports.defaultDomainSuggestionMethod = defaultDomainSuggestionMethod;
|
|
2422
2936
|
exports.defaultNameDetectionMethod = defaultNameDetectionMethod;
|
|
2423
2937
|
exports.detectName = detectName;
|
|
2424
|
-
exports.
|
|
2938
|
+
exports.detectNameForAlgorithm = detectNameForAlgorithm;
|
|
2425
2939
|
exports.detectNameFromEmail = detectNameFromEmail;
|
|
2426
2940
|
exports.domainPorts = domainPorts;
|
|
2427
2941
|
exports.getCacheStore = getCacheStore;
|
|
@@ -2434,6 +2948,7 @@ exports.isDisposableEmail = isDisposableEmail;
|
|
|
2434
2948
|
exports.isFreeEmail = isFreeEmail;
|
|
2435
2949
|
exports.isValidEmail = isValidEmail;
|
|
2436
2950
|
exports.isValidEmailDomain = isValidEmailDomain;
|
|
2951
|
+
exports.parseSmtpError = parseSmtpError;
|
|
2437
2952
|
exports.resetDefaultCache = resetDefaultCache;
|
|
2438
2953
|
exports.suggestDomain = suggestDomain;
|
|
2439
2954
|
exports.suggestEmailDomain = suggestEmailDomain;
|