@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.esm.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { isValid, parse } from 'psl';
|
|
2
2
|
import { lru } from 'tiny-lru';
|
|
3
|
-
import { promises } from 'node:dns';
|
|
4
3
|
import { stringSimilarity } from 'string-similarity-js';
|
|
4
|
+
import { promises } from 'node:dns';
|
|
5
5
|
import * as net from 'node:net';
|
|
6
|
+
import * as tls from 'node:tls';
|
|
6
7
|
|
|
7
8
|
class LRUAdapter {
|
|
8
9
|
constructor(maxSize = 1e3, ttlMs = 36e5) {
|
|
@@ -43,28 +44,23 @@ class LRUAdapter {
|
|
|
43
44
|
const DEFAULT_CACHE_OPTIONS = {
|
|
44
45
|
ttl: {
|
|
45
46
|
mx: 36e5,
|
|
46
|
-
// 1 hour
|
|
47
47
|
disposable: 864e5,
|
|
48
|
-
// 24 hours
|
|
49
48
|
free: 864e5,
|
|
50
|
-
// 24 hours
|
|
51
49
|
domainValid: 864e5,
|
|
52
|
-
// 24 hours
|
|
53
50
|
smtp: 18e5,
|
|
54
|
-
|
|
51
|
+
smtpPort: 864e5,
|
|
55
52
|
domainSuggestion: 864e5,
|
|
56
|
-
// 24 hours
|
|
57
53
|
whois: 36e5
|
|
58
|
-
// 1 hour
|
|
59
54
|
},
|
|
60
55
|
maxSize: {
|
|
61
|
-
mx:
|
|
62
|
-
disposable:
|
|
63
|
-
free:
|
|
64
|
-
domainValid:
|
|
65
|
-
smtp:
|
|
66
|
-
|
|
67
|
-
|
|
56
|
+
mx: 1e4,
|
|
57
|
+
disposable: 1e4,
|
|
58
|
+
free: 1e4,
|
|
59
|
+
domainValid: 1e4,
|
|
60
|
+
smtp: 1e4,
|
|
61
|
+
smtpPort: 1e4,
|
|
62
|
+
domainSuggestion: 1e4,
|
|
63
|
+
whois: 1e4
|
|
68
64
|
}
|
|
69
65
|
};
|
|
70
66
|
let defaultCacheInstance = null;
|
|
@@ -76,6 +72,7 @@ function getDefaultCache() {
|
|
|
76
72
|
free: new LRUAdapter(DEFAULT_CACHE_OPTIONS.maxSize.free, DEFAULT_CACHE_OPTIONS.ttl.free),
|
|
77
73
|
domainValid: new LRUAdapter(DEFAULT_CACHE_OPTIONS.maxSize.domainValid, DEFAULT_CACHE_OPTIONS.ttl.domainValid),
|
|
78
74
|
smtp: new LRUAdapter(DEFAULT_CACHE_OPTIONS.maxSize.smtp, DEFAULT_CACHE_OPTIONS.ttl.smtp),
|
|
75
|
+
smtpPort: new LRUAdapter(DEFAULT_CACHE_OPTIONS.maxSize.smtpPort, DEFAULT_CACHE_OPTIONS.ttl.smtpPort),
|
|
79
76
|
domainSuggestion: new LRUAdapter(DEFAULT_CACHE_OPTIONS.maxSize.domainSuggestion, DEFAULT_CACHE_OPTIONS.ttl.domainSuggestion),
|
|
80
77
|
whois: new LRUAdapter(DEFAULT_CACHE_OPTIONS.maxSize.whois, DEFAULT_CACHE_OPTIONS.ttl.whois)
|
|
81
78
|
};
|
|
@@ -92,6 +89,7 @@ function clearDefaultCache() {
|
|
|
92
89
|
defaultCacheInstance.free.clear();
|
|
93
90
|
defaultCacheInstance.domainValid.clear();
|
|
94
91
|
defaultCacheInstance.smtp.clear();
|
|
92
|
+
defaultCacheInstance.smtpPort.clear();
|
|
95
93
|
defaultCacheInstance.domainSuggestion.clear();
|
|
96
94
|
defaultCacheInstance.whois.clear();
|
|
97
95
|
}
|
|
@@ -100,41 +98,7 @@ function resetDefaultCache() {
|
|
|
100
98
|
defaultCacheInstance = null;
|
|
101
99
|
}
|
|
102
100
|
|
|
103
|
-
|
|
104
|
-
const { domain, cache, logger } = params;
|
|
105
|
-
const log = logger || (() => {
|
|
106
|
-
});
|
|
107
|
-
const cacheStore = getCacheStore(cache, "mx");
|
|
108
|
-
const cached = await cacheStore.get(domain);
|
|
109
|
-
if (cached !== null && cached !== void 0) {
|
|
110
|
-
log(`[resolveMxRecords] Cache hit for ${domain}: ${cached.length} MX records`);
|
|
111
|
-
return cached;
|
|
112
|
-
}
|
|
113
|
-
log(`[resolveMxRecords] Performing DNS MX lookup for ${domain}`);
|
|
114
|
-
try {
|
|
115
|
-
const records = await promises.resolveMx(domain);
|
|
116
|
-
records.sort((a, b) => {
|
|
117
|
-
if (a.priority < b.priority) {
|
|
118
|
-
return -1;
|
|
119
|
-
}
|
|
120
|
-
if (a.priority > b.priority) {
|
|
121
|
-
return 1;
|
|
122
|
-
}
|
|
123
|
-
return 0;
|
|
124
|
-
});
|
|
125
|
-
const exchanges = records.map((record) => record.exchange);
|
|
126
|
-
log(`[resolveMxRecords] Found ${exchanges.length} MX records for ${domain}: [${exchanges.join(", ")}]`);
|
|
127
|
-
await cacheStore.set(domain, exchanges);
|
|
128
|
-
log(`[resolveMxRecords] Cached ${exchanges.length} MX records for ${domain}`);
|
|
129
|
-
return exchanges;
|
|
130
|
-
} catch (error) {
|
|
131
|
-
log(`[resolveMxRecords] MX lookup failed for ${domain}, caching empty result`);
|
|
132
|
-
await cacheStore.set(domain, []);
|
|
133
|
-
throw error;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const COMMON_EMAIL_DOMAINS = [
|
|
101
|
+
const commonEmailDomains = [
|
|
138
102
|
// Popular free email providers
|
|
139
103
|
"gmail.com",
|
|
140
104
|
"yahoo.com",
|
|
@@ -219,7 +183,7 @@ function getSimilarityThreshold(domain) {
|
|
|
219
183
|
return 0.75;
|
|
220
184
|
}
|
|
221
185
|
function defaultDomainSuggestionMethod(domain, commonDomains) {
|
|
222
|
-
const domainsToCheck = commonDomains ||
|
|
186
|
+
const domainsToCheck = commonDomains || commonEmailDomains;
|
|
223
187
|
const lowerDomain = domain.toLowerCase();
|
|
224
188
|
if (domainsToCheck.includes(lowerDomain)) {
|
|
225
189
|
return null;
|
|
@@ -275,7 +239,7 @@ async function defaultDomainSuggestionMethodImpl(domain, commonDomains, cache) {
|
|
|
275
239
|
if (!domain || domain.length < 3) {
|
|
276
240
|
return null;
|
|
277
241
|
}
|
|
278
|
-
const domainsToCheck = commonDomains ||
|
|
242
|
+
const domainsToCheck = commonDomains || commonEmailDomains;
|
|
279
243
|
const lowerDomain = domain.toLowerCase();
|
|
280
244
|
const cacheKey = `${lowerDomain}:${domainsToCheck.length}`;
|
|
281
245
|
const cacheStore = getCacheStore(cache, "domainSuggestion");
|
|
@@ -371,15 +335,107 @@ async function suggestEmailDomain(email, commonDomains, cache) {
|
|
|
371
335
|
return null;
|
|
372
336
|
}
|
|
373
337
|
function isCommonDomain(domain, commonDomains) {
|
|
374
|
-
const domainsToCheck = commonDomains ||
|
|
338
|
+
const domainsToCheck = commonDomains || commonEmailDomains;
|
|
375
339
|
return domainsToCheck.includes(domain.toLowerCase());
|
|
376
340
|
}
|
|
377
341
|
function getDomainSimilarity(domain1, domain2) {
|
|
378
342
|
return stringSimilarity(domain1.toLowerCase(), domain2.toLowerCase());
|
|
379
343
|
}
|
|
380
344
|
|
|
381
|
-
|
|
382
|
-
|
|
345
|
+
async function isValidEmailDomain(emailOrDomain, cache) {
|
|
346
|
+
let [localPart, emailDomain] = (emailOrDomain === null || emailOrDomain === void 0 ? void 0 : emailOrDomain.split("@")) || [];
|
|
347
|
+
if (!emailDomain) {
|
|
348
|
+
emailDomain = localPart;
|
|
349
|
+
}
|
|
350
|
+
if (!emailDomain) {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
const cacheStore = getCacheStore(cache, "domainValid");
|
|
354
|
+
const cached = await cacheStore.get(emailDomain);
|
|
355
|
+
if (cached !== null && cached !== void 0) {
|
|
356
|
+
return cached.isValid;
|
|
357
|
+
}
|
|
358
|
+
try {
|
|
359
|
+
const isValidResult = isValid(emailDomain) || false;
|
|
360
|
+
const richResult = {
|
|
361
|
+
isValid: isValidResult,
|
|
362
|
+
hasMX: false,
|
|
363
|
+
// MX not checked in this function
|
|
364
|
+
checkedAt: Date.now()
|
|
365
|
+
};
|
|
366
|
+
await cacheStore.set(emailDomain, richResult);
|
|
367
|
+
return isValidResult;
|
|
368
|
+
} catch (validationError) {
|
|
369
|
+
const errorResult = {
|
|
370
|
+
isValid: false,
|
|
371
|
+
hasMX: false,
|
|
372
|
+
checkedAt: Date.now()
|
|
373
|
+
};
|
|
374
|
+
await cacheStore.set(emailDomain, errorResult);
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
function isValidEmail(emailAddress) {
|
|
379
|
+
if (!emailAddress || typeof emailAddress !== "string") {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
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,}))$/;
|
|
383
|
+
const emailLower = emailAddress.toLowerCase();
|
|
384
|
+
if (emailLower.indexOf(".+") !== -1)
|
|
385
|
+
return false;
|
|
386
|
+
if (emailLower.indexOf("..") !== -1)
|
|
387
|
+
return false;
|
|
388
|
+
if (emailLower.startsWith(".") || emailLower.endsWith("."))
|
|
389
|
+
return false;
|
|
390
|
+
const parts = emailAddress.split("@");
|
|
391
|
+
if (parts.length !== 2)
|
|
392
|
+
return false;
|
|
393
|
+
const [localPart, domain] = parts;
|
|
394
|
+
if (!localPart || !domain)
|
|
395
|
+
return false;
|
|
396
|
+
if (localPart.length > 64)
|
|
397
|
+
return false;
|
|
398
|
+
if (domain.length > 253)
|
|
399
|
+
return false;
|
|
400
|
+
return re.test(emailLower);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async function resolveMxRecords(params) {
|
|
404
|
+
const { domain, cache, logger } = params;
|
|
405
|
+
const log = logger || (() => {
|
|
406
|
+
});
|
|
407
|
+
const cacheStore = getCacheStore(cache, "mx");
|
|
408
|
+
const cached = await cacheStore.get(domain);
|
|
409
|
+
if (cached !== null && cached !== void 0) {
|
|
410
|
+
log(`[resolveMxRecords] Cache hit for ${domain}: ${cached === null || cached === void 0 ? void 0 : cached.length} MX records`);
|
|
411
|
+
return cached;
|
|
412
|
+
}
|
|
413
|
+
log(`[resolveMxRecords] Performing DNS MX lookup for ${domain}`);
|
|
414
|
+
try {
|
|
415
|
+
const records = await promises.resolveMx(domain);
|
|
416
|
+
records === null || records === void 0 ? void 0 : records.sort((a, b) => {
|
|
417
|
+
if (a.priority < b.priority) {
|
|
418
|
+
return -1;
|
|
419
|
+
}
|
|
420
|
+
if (a.priority > b.priority) {
|
|
421
|
+
return 1;
|
|
422
|
+
}
|
|
423
|
+
return 0;
|
|
424
|
+
});
|
|
425
|
+
const exchanges = records === null || records === void 0 ? void 0 : records.map((record) => record.exchange);
|
|
426
|
+
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(", ")}]`);
|
|
427
|
+
await cacheStore.set(domain, exchanges);
|
|
428
|
+
log(`[resolveMxRecords] Cached ${exchanges === null || exchanges === void 0 ? void 0 : exchanges.length} MX records for ${domain}`);
|
|
429
|
+
return exchanges;
|
|
430
|
+
} catch (error) {
|
|
431
|
+
log(`[resolveMxRecords] MX lookup failed for ${domain}, caching empty result`);
|
|
432
|
+
await cacheStore.set(domain, []);
|
|
433
|
+
throw error;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const nameSeparator = [".", "_", "-"];
|
|
438
|
+
const commonNameSuffixes = [
|
|
383
439
|
"mail",
|
|
384
440
|
"email",
|
|
385
441
|
"contact",
|
|
@@ -394,7 +450,7 @@ const COMMON_NAME_SUFFIXES = [
|
|
|
394
450
|
"notifications",
|
|
395
451
|
"alerts"
|
|
396
452
|
];
|
|
397
|
-
const
|
|
453
|
+
const contextualSuffixes = [
|
|
398
454
|
"dev",
|
|
399
455
|
"company",
|
|
400
456
|
"team",
|
|
@@ -426,7 +482,7 @@ const COMMON_TITLES = [
|
|
|
426
482
|
"father",
|
|
427
483
|
"sister"
|
|
428
484
|
];
|
|
429
|
-
const
|
|
485
|
+
const commonFirstName = /* @__PURE__ */ new Set([
|
|
430
486
|
// English names
|
|
431
487
|
"james",
|
|
432
488
|
"john",
|
|
@@ -604,7 +660,7 @@ const COMMON_FIRST_NAMES = /* @__PURE__ */ new Set([
|
|
|
604
660
|
"sekou",
|
|
605
661
|
"mariama"
|
|
606
662
|
]);
|
|
607
|
-
const
|
|
663
|
+
const commonLastName = /* @__PURE__ */ new Set([
|
|
608
664
|
// English surnames
|
|
609
665
|
"smith",
|
|
610
666
|
"johnson",
|
|
@@ -724,19 +780,19 @@ function isYearLike(str) {
|
|
|
724
780
|
return /^(19|20)\d{2}$/.test(str);
|
|
725
781
|
}
|
|
726
782
|
function isKnownFirstName(str) {
|
|
727
|
-
return
|
|
783
|
+
return commonFirstName.has(str.toLowerCase());
|
|
728
784
|
}
|
|
729
785
|
function isKnownLastName(str) {
|
|
730
|
-
return
|
|
786
|
+
return commonLastName.has(str.toLowerCase());
|
|
731
787
|
}
|
|
732
788
|
function isTitle(str) {
|
|
733
789
|
return COMMON_TITLES.includes(str.toLowerCase().replace(".", ""));
|
|
734
790
|
}
|
|
735
791
|
function getFirstNameScore(str) {
|
|
736
792
|
const lower = str.toLowerCase();
|
|
737
|
-
if (
|
|
793
|
+
if (commonFirstName.has(lower))
|
|
738
794
|
return 1;
|
|
739
|
-
if (
|
|
795
|
+
if (commonLastName.has(lower))
|
|
740
796
|
return 0.3;
|
|
741
797
|
if (str.length >= 2 && str.length <= 15 && /^[a-zA-Z]+$/.test(str))
|
|
742
798
|
return 0.5;
|
|
@@ -744,9 +800,9 @@ function getFirstNameScore(str) {
|
|
|
744
800
|
}
|
|
745
801
|
function getLastNameScore(str) {
|
|
746
802
|
const lower = str.toLowerCase();
|
|
747
|
-
if (
|
|
803
|
+
if (commonLastName.has(lower))
|
|
748
804
|
return 1;
|
|
749
|
-
if (
|
|
805
|
+
if (commonFirstName.has(lower))
|
|
750
806
|
return 0.3;
|
|
751
807
|
if (str.length >= 2 && str.length <= 20 && /^[a-zA-Z]+$/.test(str))
|
|
752
808
|
return 0.5;
|
|
@@ -855,7 +911,7 @@ function isLikelyName(str, allowNumbers = false, allowSingleLetter = false) {
|
|
|
855
911
|
return false;
|
|
856
912
|
if (str.length === 1 && allowSingleLetter && /^[a-zA-Z]$/.test(str))
|
|
857
913
|
return true;
|
|
858
|
-
if (
|
|
914
|
+
if (commonNameSuffixes.includes(str.toLowerCase()))
|
|
859
915
|
return false;
|
|
860
916
|
if (allowNumbers) {
|
|
861
917
|
if (/^\d+$/.test(str))
|
|
@@ -917,7 +973,7 @@ function defaultNameDetectionMethod(email) {
|
|
|
917
973
|
}
|
|
918
974
|
}
|
|
919
975
|
if (!firstName && !lastName) {
|
|
920
|
-
for (const separator of
|
|
976
|
+
for (const separator of nameSeparator) {
|
|
921
977
|
if (cleanedLocal.includes(separator)) {
|
|
922
978
|
const parts = cleanedLocal.split(separator).filter((p) => p.length > 0);
|
|
923
979
|
if (parts.length === 2) {
|
|
@@ -979,7 +1035,7 @@ function defaultNameDetectionMethod(email) {
|
|
|
979
1035
|
const firstParsed = parseCompositeNamePart(first);
|
|
980
1036
|
const middleParsed = parseCompositeNamePart(middle);
|
|
981
1037
|
const lastParsed = parseCompositeNamePart(last);
|
|
982
|
-
const isLastSuffix =
|
|
1038
|
+
const isLastSuffix = commonNameSuffixes.includes(last.toLowerCase()) || contextualSuffixes.includes(last.toLowerCase()) || isYearLike(last);
|
|
983
1039
|
if (isLastSuffix) {
|
|
984
1040
|
if (isLikelyName(first, true, true) && isLikelyName(middle, true, true)) {
|
|
985
1041
|
const cleanedFirst = firstParsed.hasNumbers ? firstParsed.cleaned : first;
|
|
@@ -1012,7 +1068,7 @@ function defaultNameDetectionMethod(email) {
|
|
|
1012
1068
|
} else if (parts.length > 3) {
|
|
1013
1069
|
const firstPart = parts[0];
|
|
1014
1070
|
const lastPartLower = parts[parts.length - 1].toLowerCase();
|
|
1015
|
-
const isLastPartSuffix =
|
|
1071
|
+
const isLastPartSuffix = commonNameSuffixes.includes(lastPartLower) || contextualSuffixes.includes(lastPartLower) || isYearLike(parts[parts.length - 1]);
|
|
1016
1072
|
const effectiveLastIndex = isLastPartSuffix ? parts.length - 2 : parts.length - 1;
|
|
1017
1073
|
const lastToUse = effectiveLastIndex >= 0 ? parts[effectiveLastIndex] : null;
|
|
1018
1074
|
if (lastToUse && isLikelyName(firstPart, true, true) && isLikelyName(lastToUse, true, true)) {
|
|
@@ -1095,7 +1151,7 @@ function detectNameFromEmail(params) {
|
|
|
1095
1151
|
}
|
|
1096
1152
|
return defaultNameDetectionMethod(email);
|
|
1097
1153
|
}
|
|
1098
|
-
function
|
|
1154
|
+
function cleanNameForAlgorithm(name) {
|
|
1099
1155
|
if (!name)
|
|
1100
1156
|
return "";
|
|
1101
1157
|
let cleaned = name.replace(/[._*]/g, "");
|
|
@@ -1105,13 +1161,13 @@ function cleanNameForAlgrothin(name) {
|
|
|
1105
1161
|
}
|
|
1106
1162
|
return cleaned;
|
|
1107
1163
|
}
|
|
1108
|
-
function
|
|
1164
|
+
function detectNameForAlgorithm(email) {
|
|
1109
1165
|
const detectedName = detectName(email);
|
|
1110
1166
|
if (!detectedName) {
|
|
1111
1167
|
return null;
|
|
1112
1168
|
}
|
|
1113
|
-
const cleanedFirstName = detectedName.firstName ?
|
|
1114
|
-
const cleanedLastName = detectedName.lastName ?
|
|
1169
|
+
const cleanedFirstName = detectedName.firstName ? cleanNameForAlgorithm(detectedName.firstName) : void 0;
|
|
1170
|
+
const cleanedLastName = detectedName.lastName ? cleanNameForAlgorithm(detectedName.lastName) : void 0;
|
|
1115
1171
|
if (!cleanedFirstName && !cleanedLastName) {
|
|
1116
1172
|
return null;
|
|
1117
1173
|
}
|
|
@@ -1126,6 +1182,156 @@ function detectName(email) {
|
|
|
1126
1182
|
return detectNameFromEmail({ email });
|
|
1127
1183
|
}
|
|
1128
1184
|
|
|
1185
|
+
var VerificationErrorCode;
|
|
1186
|
+
(function(VerificationErrorCode2) {
|
|
1187
|
+
VerificationErrorCode2["invalidFormat"] = "INVALID_FORMAT";
|
|
1188
|
+
VerificationErrorCode2["invalidDomain"] = "INVALID_DOMAIN";
|
|
1189
|
+
VerificationErrorCode2["noMxRecords"] = "NO_MX_RECORDS";
|
|
1190
|
+
VerificationErrorCode2["smtpConnectionFailed"] = "SMTP_CONNECTION_FAILED";
|
|
1191
|
+
VerificationErrorCode2["smtpTimeout"] = "SMTP_TIMEOUT";
|
|
1192
|
+
VerificationErrorCode2["mailboxNotFound"] = "MAILBOX_NOT_FOUND";
|
|
1193
|
+
VerificationErrorCode2["mailboxFull"] = "MAILBOX_FULL";
|
|
1194
|
+
VerificationErrorCode2["networkError"] = "NETWORK_ERROR";
|
|
1195
|
+
VerificationErrorCode2["disposableEmail"] = "DISPOSABLE_EMAIL";
|
|
1196
|
+
VerificationErrorCode2["freeEmailProvider"] = "FREE_EMAIL_PROVIDER";
|
|
1197
|
+
})(VerificationErrorCode || (VerificationErrorCode = {}));
|
|
1198
|
+
var EmailProvider;
|
|
1199
|
+
(function(EmailProvider2) {
|
|
1200
|
+
EmailProvider2["gmail"] = "gmail";
|
|
1201
|
+
EmailProvider2["hotmailB2b"] = "hotmail_b2b";
|
|
1202
|
+
EmailProvider2["hotmailB2c"] = "hotmail_b2c";
|
|
1203
|
+
EmailProvider2["proofpoint"] = "proofpoint";
|
|
1204
|
+
EmailProvider2["mimecast"] = "mimecast";
|
|
1205
|
+
EmailProvider2["yahoo"] = "yahoo";
|
|
1206
|
+
EmailProvider2["everythingElse"] = "everything_else";
|
|
1207
|
+
})(EmailProvider || (EmailProvider = {}));
|
|
1208
|
+
function parseSmtpError(errorMessage) {
|
|
1209
|
+
const lowerError = errorMessage.toLowerCase();
|
|
1210
|
+
const networkErrorPatterns = [
|
|
1211
|
+
"etimedout",
|
|
1212
|
+
"econnrefused",
|
|
1213
|
+
"enotfound",
|
|
1214
|
+
"econnreset",
|
|
1215
|
+
"socket hang up",
|
|
1216
|
+
"connection_timeout",
|
|
1217
|
+
"socket_timeout",
|
|
1218
|
+
"connection_error",
|
|
1219
|
+
"connection_closed"
|
|
1220
|
+
];
|
|
1221
|
+
const isNetworkError = networkErrorPatterns.some((pattern) => lowerError.includes(pattern));
|
|
1222
|
+
if (isNetworkError) {
|
|
1223
|
+
return {
|
|
1224
|
+
isDisabled: false,
|
|
1225
|
+
hasFullInbox: false,
|
|
1226
|
+
isInvalid: true,
|
|
1227
|
+
isCatchAll: false
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
const disabledPatterns = [
|
|
1231
|
+
"account disabled",
|
|
1232
|
+
"account is disabled",
|
|
1233
|
+
"user disabled",
|
|
1234
|
+
"user is disabled",
|
|
1235
|
+
"account locked",
|
|
1236
|
+
"account is locked",
|
|
1237
|
+
"user blocked",
|
|
1238
|
+
"user is blocked",
|
|
1239
|
+
"mailbox disabled",
|
|
1240
|
+
"delivery not authorized",
|
|
1241
|
+
"message rejected",
|
|
1242
|
+
"access denied",
|
|
1243
|
+
"permission denied",
|
|
1244
|
+
"recipient unknown",
|
|
1245
|
+
"recipient address rejected",
|
|
1246
|
+
"user unknown",
|
|
1247
|
+
"address unknown",
|
|
1248
|
+
"invalid recipient",
|
|
1249
|
+
"not a valid recipient",
|
|
1250
|
+
"recipient does not exist",
|
|
1251
|
+
"no such user",
|
|
1252
|
+
"user does not exist",
|
|
1253
|
+
"mailbox unavailable",
|
|
1254
|
+
"recipient unavailable",
|
|
1255
|
+
"address rejected",
|
|
1256
|
+
"550",
|
|
1257
|
+
"551",
|
|
1258
|
+
"553",
|
|
1259
|
+
"not_found",
|
|
1260
|
+
"ambiguous"
|
|
1261
|
+
];
|
|
1262
|
+
const fullInboxPatterns = [
|
|
1263
|
+
"mailbox full",
|
|
1264
|
+
"inbox full",
|
|
1265
|
+
"quota exceeded",
|
|
1266
|
+
"over quota",
|
|
1267
|
+
"storage limit exceeded",
|
|
1268
|
+
"message too large",
|
|
1269
|
+
"insufficient storage",
|
|
1270
|
+
"mailbox over quota",
|
|
1271
|
+
"over the quota",
|
|
1272
|
+
"mailbox size limit exceeded",
|
|
1273
|
+
"account over quota",
|
|
1274
|
+
"storage space",
|
|
1275
|
+
"overquota",
|
|
1276
|
+
"452",
|
|
1277
|
+
"552",
|
|
1278
|
+
"over_quota"
|
|
1279
|
+
];
|
|
1280
|
+
const catchAllPatterns = [
|
|
1281
|
+
"accept all mail",
|
|
1282
|
+
"catch-all",
|
|
1283
|
+
"catchall",
|
|
1284
|
+
"wildcard",
|
|
1285
|
+
"accepts any recipient",
|
|
1286
|
+
"recipient address accepted"
|
|
1287
|
+
];
|
|
1288
|
+
const rateLimitPatterns = [
|
|
1289
|
+
"receiving mail at a rate that",
|
|
1290
|
+
"rate limit",
|
|
1291
|
+
"too many messages",
|
|
1292
|
+
"temporarily rejected",
|
|
1293
|
+
"try again later",
|
|
1294
|
+
"greylisted",
|
|
1295
|
+
"greylist",
|
|
1296
|
+
"deferring",
|
|
1297
|
+
"temporarily deferred",
|
|
1298
|
+
"421",
|
|
1299
|
+
"450",
|
|
1300
|
+
"451",
|
|
1301
|
+
"temporary_failure"
|
|
1302
|
+
];
|
|
1303
|
+
const isDisabled = disabledPatterns.some((pattern) => lowerError.includes(pattern)) || lowerError.startsWith("550") || lowerError.startsWith("551") || lowerError.startsWith("553");
|
|
1304
|
+
const hasFullInbox = fullInboxPatterns.some((pattern) => lowerError.includes(pattern)) || lowerError.startsWith("452") || lowerError.startsWith("552");
|
|
1305
|
+
const isCatchAll = catchAllPatterns.some((pattern) => lowerError.includes(pattern));
|
|
1306
|
+
const isInvalid = !isDisabled && !hasFullInbox && !isCatchAll && !rateLimitPatterns.some((pattern) => lowerError.includes(pattern)) && !lowerError.startsWith("421") && !lowerError.startsWith("450") && !lowerError.startsWith("451");
|
|
1307
|
+
return {
|
|
1308
|
+
isDisabled,
|
|
1309
|
+
hasFullInbox,
|
|
1310
|
+
isInvalid,
|
|
1311
|
+
isCatchAll
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
var SMTPStep;
|
|
1315
|
+
(function(SMTPStep2) {
|
|
1316
|
+
SMTPStep2["greeting"] = "GREETING";
|
|
1317
|
+
SMTPStep2["ehlo"] = "EHLO";
|
|
1318
|
+
SMTPStep2["helo"] = "HELO";
|
|
1319
|
+
SMTPStep2["startTls"] = "STARTTLS";
|
|
1320
|
+
SMTPStep2["mailFrom"] = "MAIL_FROM";
|
|
1321
|
+
SMTPStep2["rcptTo"] = "RCPT_TO";
|
|
1322
|
+
SMTPStep2["vrfy"] = "VRFY";
|
|
1323
|
+
SMTPStep2["quit"] = "QUIT";
|
|
1324
|
+
})(SMTPStep || (SMTPStep = {}));
|
|
1325
|
+
|
|
1326
|
+
function isIPAddress(host) {
|
|
1327
|
+
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
1328
|
+
if (ipv4Regex.test(host)) {
|
|
1329
|
+
const octets = host.split(".");
|
|
1330
|
+
return octets.every((octet) => parseInt(octet, 10) <= 255);
|
|
1331
|
+
}
|
|
1332
|
+
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}:)$/;
|
|
1333
|
+
return ipv6Regex.test(host);
|
|
1334
|
+
}
|
|
1129
1335
|
function isOverQuota(smtpReply) {
|
|
1130
1336
|
return Boolean(smtpReply && /(over quota)/gi.test(smtpReply));
|
|
1131
1337
|
}
|
|
@@ -1135,182 +1341,469 @@ function isInvalidMailboxError(smtpReply) {
|
|
|
1135
1341
|
function isMultilineGreet(smtpReply) {
|
|
1136
1342
|
return Boolean(smtpReply && /^(250|220)-/.test(smtpReply));
|
|
1137
1343
|
}
|
|
1344
|
+
const DEFAULT_PORTS = [25, 587, 465];
|
|
1345
|
+
const DEFAULT_TIMEOUT = 3e3;
|
|
1346
|
+
const DEFAULT_MAX_RETRIES = 1;
|
|
1347
|
+
const PORT_CONFIGS = {
|
|
1348
|
+
25: { tls: false, starttls: true },
|
|
1349
|
+
587: { tls: false, starttls: true },
|
|
1350
|
+
465: { tls: true, starttls: false }
|
|
1351
|
+
};
|
|
1138
1352
|
async function verifyMailboxSMTP(params) {
|
|
1139
|
-
const { local, domain, mxRecords = [],
|
|
1140
|
-
const
|
|
1353
|
+
const { local, domain, mxRecords = [], options = {} } = params;
|
|
1354
|
+
const { ports = DEFAULT_PORTS, timeout = DEFAULT_TIMEOUT, maxRetries = DEFAULT_MAX_RETRIES, tls: tlsConfig = true, hostname = "localhost", useVRFY = true, cache, debug = false, sequence } = options;
|
|
1355
|
+
const log = debug ? (...args) => console.log("[SMTP]", ...args) : () => {
|
|
1356
|
+
};
|
|
1357
|
+
const createSmtpResult = (result, port, tlsUsed, mxHost2) => {
|
|
1358
|
+
const reason = result === true ? "valid" : result === null ? "ambiguous" : "not_found";
|
|
1359
|
+
const parsedError = parseSmtpError(reason);
|
|
1360
|
+
return {
|
|
1361
|
+
canConnectSmtp: result !== null,
|
|
1362
|
+
hasFullInbox: parsedError.hasFullInbox,
|
|
1363
|
+
isCatchAll: parsedError.isCatchAll,
|
|
1364
|
+
isDeliverable: result === true,
|
|
1365
|
+
isDisabled: result === false && parsedError.isDisabled,
|
|
1366
|
+
error: result === null ? reason : result === false ? reason : void 0,
|
|
1367
|
+
providerUsed: EmailProvider.everythingElse,
|
|
1368
|
+
checkedAt: Date.now()
|
|
1369
|
+
};
|
|
1141
1370
|
};
|
|
1371
|
+
const createFailureResult = (error) => ({
|
|
1372
|
+
canConnectSmtp: false,
|
|
1373
|
+
hasFullInbox: false,
|
|
1374
|
+
isCatchAll: false,
|
|
1375
|
+
isDeliverable: false,
|
|
1376
|
+
isDisabled: false,
|
|
1377
|
+
error,
|
|
1378
|
+
providerUsed: EmailProvider.everythingElse,
|
|
1379
|
+
checkedAt: Date.now()
|
|
1380
|
+
});
|
|
1142
1381
|
if (!mxRecords || mxRecords.length === 0) {
|
|
1143
|
-
|
|
1382
|
+
log("No MX records found");
|
|
1383
|
+
return {
|
|
1384
|
+
smtpResult: createFailureResult("No MX records found"),
|
|
1385
|
+
cached: false,
|
|
1386
|
+
port: 0,
|
|
1387
|
+
portCached: false
|
|
1388
|
+
};
|
|
1144
1389
|
}
|
|
1145
|
-
const
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
domain
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1390
|
+
const mxHost = mxRecords[0];
|
|
1391
|
+
log(`Verifying ${local}@${domain} via ${mxHost}`);
|
|
1392
|
+
const smtpCacheStore = cache ? getCacheStore(cache, "smtp") : null;
|
|
1393
|
+
if (smtpCacheStore) {
|
|
1394
|
+
let cachedResult;
|
|
1395
|
+
try {
|
|
1396
|
+
cachedResult = await smtpCacheStore.get(`${mxHost}:${local}@${domain}`);
|
|
1397
|
+
if (cachedResult !== void 0 && cachedResult !== null) {
|
|
1398
|
+
log(`Using cached SMTP result: ${cachedResult.isDeliverable}`);
|
|
1399
|
+
return {
|
|
1400
|
+
smtpResult: cachedResult,
|
|
1401
|
+
cached: true,
|
|
1402
|
+
port: 0,
|
|
1403
|
+
portCached: false
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
} catch (ignoredError) {
|
|
1407
|
+
cachedResult = void 0;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
const smtpPortCacheStore = cache ? getCacheStore(cache, "smtpPort") : null;
|
|
1411
|
+
if (smtpPortCacheStore) {
|
|
1412
|
+
let cachedPort;
|
|
1413
|
+
try {
|
|
1414
|
+
cachedPort = await smtpPortCacheStore.get(mxHost);
|
|
1415
|
+
} catch (ignoredError) {
|
|
1416
|
+
cachedPort = null;
|
|
1159
1417
|
}
|
|
1160
|
-
if (
|
|
1161
|
-
|
|
1418
|
+
if (cachedPort) {
|
|
1419
|
+
log(`Using cached port: ${cachedPort}`);
|
|
1420
|
+
const result = await testSMTPConnection({
|
|
1421
|
+
mxHost,
|
|
1422
|
+
port: cachedPort,
|
|
1423
|
+
local,
|
|
1424
|
+
domain,
|
|
1425
|
+
timeout,
|
|
1426
|
+
tlsConfig,
|
|
1427
|
+
hostname,
|
|
1428
|
+
useVRFY,
|
|
1429
|
+
sequence,
|
|
1430
|
+
log
|
|
1431
|
+
});
|
|
1432
|
+
const smtpResult = createSmtpResult(result);
|
|
1433
|
+
if (smtpCacheStore) {
|
|
1434
|
+
try {
|
|
1435
|
+
await smtpCacheStore.set(`${mxHost}:${local}@${domain}`, smtpResult);
|
|
1436
|
+
log(`Cached SMTP result ${result} for ${local}@${domain} via ${mxHost}`);
|
|
1437
|
+
} catch (ignoredError) {
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
return { smtpResult, cached: false, port: cachedPort, portCached: true };
|
|
1162
1441
|
}
|
|
1163
1442
|
}
|
|
1164
|
-
|
|
1443
|
+
for (const port of ports) {
|
|
1444
|
+
log(`Testing port ${port}`);
|
|
1445
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
1446
|
+
if (attempt > 0) {
|
|
1447
|
+
const delay = Math.min(200 * 2 ** (attempt - 1), 800);
|
|
1448
|
+
log(`Retry ${attempt + 1}, waiting ${delay}ms`);
|
|
1449
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1450
|
+
}
|
|
1451
|
+
const result = await testSMTPConnection({
|
|
1452
|
+
mxHost,
|
|
1453
|
+
port,
|
|
1454
|
+
local,
|
|
1455
|
+
domain,
|
|
1456
|
+
timeout,
|
|
1457
|
+
tlsConfig,
|
|
1458
|
+
hostname,
|
|
1459
|
+
useVRFY,
|
|
1460
|
+
sequence,
|
|
1461
|
+
log
|
|
1462
|
+
});
|
|
1463
|
+
const smtpResult = createSmtpResult(result);
|
|
1464
|
+
if (smtpCacheStore) {
|
|
1465
|
+
try {
|
|
1466
|
+
await smtpCacheStore.set(`${mxHost}:${local}@${domain}`, smtpResult);
|
|
1467
|
+
log(`Cached SMTP result ${result} for ${local}@${domain} via ${mxHost}`);
|
|
1468
|
+
} catch (ignoredError) {
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
if (result !== null) {
|
|
1472
|
+
if (smtpPortCacheStore) {
|
|
1473
|
+
try {
|
|
1474
|
+
await smtpPortCacheStore.set(mxHost, port);
|
|
1475
|
+
log(`Cached port ${port} for ${mxHost}`);
|
|
1476
|
+
} catch (ignoredError) {
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
return { smtpResult, cached: false, port, portCached: false };
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
log("All ports failed");
|
|
1484
|
+
return {
|
|
1485
|
+
smtpResult: createFailureResult("All SMTP connection attempts failed"),
|
|
1486
|
+
cached: false,
|
|
1487
|
+
port: 0,
|
|
1488
|
+
portCached: false
|
|
1489
|
+
};
|
|
1165
1490
|
}
|
|
1166
|
-
async function
|
|
1167
|
-
const {
|
|
1491
|
+
async function testSMTPConnection(params) {
|
|
1492
|
+
const { mxHost, port, local, domain, timeout, tlsConfig, hostname, useVRFY, sequence, log } = params;
|
|
1493
|
+
const portConfig = PORT_CONFIGS[port] || { tls: false, starttls: false };
|
|
1494
|
+
const useTLS = tlsConfig !== false && (portConfig.tls || portConfig.starttls);
|
|
1495
|
+
const implicitTLS = portConfig.tls;
|
|
1496
|
+
const defaultSequence = {
|
|
1497
|
+
steps: [SMTPStep.greeting, SMTPStep.ehlo, SMTPStep.mailFrom, SMTPStep.rcptTo]
|
|
1498
|
+
};
|
|
1499
|
+
const activeSequence = sequence || defaultSequence;
|
|
1500
|
+
if (port === 25) {
|
|
1501
|
+
activeSequence.steps = activeSequence.steps.map((step) => step === SMTPStep.ehlo ? SMTPStep.helo : step);
|
|
1502
|
+
}
|
|
1503
|
+
const tlsOptions = {
|
|
1504
|
+
host: mxHost,
|
|
1505
|
+
servername: isIPAddress(mxHost) ? void 0 : mxHost,
|
|
1506
|
+
// Don't set servername for IP addresses
|
|
1507
|
+
rejectUnauthorized: false,
|
|
1508
|
+
minVersion: "TLSv1.2",
|
|
1509
|
+
...typeof tlsConfig === "object" ? tlsConfig : {}
|
|
1510
|
+
};
|
|
1168
1511
|
return new Promise((resolve) => {
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
});
|
|
1174
|
-
let resTimeout = null;
|
|
1512
|
+
let socket;
|
|
1513
|
+
let buffer = "";
|
|
1514
|
+
let isTLS = implicitTLS;
|
|
1515
|
+
let currentStepIndex = 0;
|
|
1175
1516
|
let resolved = false;
|
|
1176
|
-
let
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
return;
|
|
1180
|
-
cleaned = true;
|
|
1181
|
-
if (resTimeout) {
|
|
1182
|
-
clearTimeout(resTimeout);
|
|
1183
|
-
resTimeout = null;
|
|
1184
|
-
}
|
|
1185
|
-
if (socket && !socket.destroyed) {
|
|
1186
|
-
socket.removeAllListeners();
|
|
1187
|
-
socket.destroy();
|
|
1188
|
-
}
|
|
1189
|
-
};
|
|
1190
|
-
const ret = (result) => {
|
|
1517
|
+
let supportsSTARTTLS = false;
|
|
1518
|
+
let supportsVRFY = false;
|
|
1519
|
+
let cleanup = () => {
|
|
1191
1520
|
if (resolved)
|
|
1192
1521
|
return;
|
|
1193
1522
|
resolved = true;
|
|
1194
|
-
|
|
1195
|
-
log("[verifyMailboxSMTP] closing socket");
|
|
1523
|
+
try {
|
|
1196
1524
|
socket === null || socket === void 0 ? void 0 : socket.write("QUIT\r\n");
|
|
1197
|
-
|
|
1525
|
+
} catch {
|
|
1198
1526
|
}
|
|
1527
|
+
setTimeout(() => socket === null || socket === void 0 ? void 0 : socket.destroy(), 100);
|
|
1528
|
+
};
|
|
1529
|
+
const finish = (result, reason) => {
|
|
1530
|
+
if (resolved)
|
|
1531
|
+
return;
|
|
1532
|
+
log(`${port}: ${reason || (result ? "valid" : "invalid")}`);
|
|
1199
1533
|
cleanup();
|
|
1200
1534
|
resolve(result);
|
|
1201
1535
|
};
|
|
1202
|
-
const
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1536
|
+
const sendCommand = (cmd) => {
|
|
1537
|
+
if (resolved)
|
|
1538
|
+
return;
|
|
1539
|
+
log(`\u2192 ${cmd}`);
|
|
1540
|
+
socket === null || socket === void 0 ? void 0 : socket.write(`${cmd}\r
|
|
1541
|
+
`);
|
|
1542
|
+
};
|
|
1543
|
+
const nextStep = () => {
|
|
1544
|
+
currentStepIndex++;
|
|
1545
|
+
if (currentStepIndex >= activeSequence.steps.length) {
|
|
1546
|
+
finish(true, "sequence_complete");
|
|
1547
|
+
return;
|
|
1210
1548
|
}
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1549
|
+
executeStep(activeSequence.steps[currentStepIndex]);
|
|
1550
|
+
};
|
|
1551
|
+
const executeStep = (step) => {
|
|
1552
|
+
if (resolved)
|
|
1553
|
+
return;
|
|
1554
|
+
switch (step) {
|
|
1555
|
+
case SMTPStep.ehlo:
|
|
1556
|
+
sendCommand(`EHLO ${hostname}`);
|
|
1557
|
+
break;
|
|
1558
|
+
case SMTPStep.helo:
|
|
1559
|
+
sendCommand(`HELO ${domain}`);
|
|
1560
|
+
break;
|
|
1561
|
+
case SMTPStep.greeting:
|
|
1562
|
+
break;
|
|
1563
|
+
case SMTPStep.startTls:
|
|
1564
|
+
sendCommand("STARTTLS");
|
|
1565
|
+
break;
|
|
1566
|
+
case SMTPStep.mailFrom: {
|
|
1567
|
+
const from = activeSequence.from || "<>";
|
|
1568
|
+
sendCommand(`MAIL FROM:${from}`);
|
|
1569
|
+
break;
|
|
1570
|
+
}
|
|
1571
|
+
case SMTPStep.rcptTo:
|
|
1572
|
+
sendCommand(`RCPT TO:<${local}@${domain}>`);
|
|
1573
|
+
break;
|
|
1574
|
+
case SMTPStep.vrfy: {
|
|
1575
|
+
const vrfyTarget = activeSequence.vrfyTarget || local;
|
|
1576
|
+
sendCommand(`VRFY ${vrfyTarget}`);
|
|
1577
|
+
break;
|
|
1578
|
+
}
|
|
1579
|
+
case SMTPStep.quit:
|
|
1580
|
+
sendCommand("QUIT");
|
|
1581
|
+
break;
|
|
1214
1582
|
}
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1583
|
+
};
|
|
1584
|
+
const processResponse = (response) => {
|
|
1585
|
+
if (resolved)
|
|
1586
|
+
return;
|
|
1587
|
+
const code = response.substring(0, 3);
|
|
1588
|
+
const isMultiline = response.length > 3 && response[3] === "-";
|
|
1589
|
+
log(`\u2190 ${response}`);
|
|
1590
|
+
if (isMultilineGreet(response)) {
|
|
1591
|
+
return;
|
|
1218
1592
|
}
|
|
1219
|
-
if (
|
|
1593
|
+
if (isOverQuota(response)) {
|
|
1594
|
+
finish(false, "over_quota");
|
|
1220
1595
|
return;
|
|
1221
|
-
if (messages.length > 0) {
|
|
1222
|
-
const message = messages.shift();
|
|
1223
|
-
log("[verifyMailboxSMTP] writing message", message);
|
|
1224
|
-
return socket.write(`${message}\r
|
|
1225
|
-
`);
|
|
1226
1596
|
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1597
|
+
if (isInvalidMailboxError(response)) {
|
|
1598
|
+
finish(false, "not_found");
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
if (isMultiline) {
|
|
1602
|
+
const currentStep2 = activeSequence.steps[currentStepIndex];
|
|
1603
|
+
if (currentStep2 === SMTPStep.ehlo && code === "250") {
|
|
1604
|
+
const upper = response.toUpperCase();
|
|
1605
|
+
if (upper.includes("STARTTLS"))
|
|
1606
|
+
supportsSTARTTLS = true;
|
|
1607
|
+
if (upper.includes("VRFY"))
|
|
1608
|
+
supportsVRFY = true;
|
|
1609
|
+
}
|
|
1610
|
+
if (currentStep2 === SMTPStep.helo && code === "250") {
|
|
1611
|
+
const upper = response.toUpperCase();
|
|
1612
|
+
if (upper.includes("VRFY"))
|
|
1613
|
+
supportsVRFY = true;
|
|
1614
|
+
}
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
if (!response.includes("220") && !response.includes("250") && !response.includes("550") && !response.includes("552")) {
|
|
1618
|
+
finish(null, "unrecognized_response");
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
1621
|
+
const currentStep = activeSequence.steps[currentStepIndex];
|
|
1622
|
+
switch (currentStep) {
|
|
1623
|
+
case SMTPStep.greeting:
|
|
1624
|
+
if (code.startsWith("220")) {
|
|
1625
|
+
nextStep();
|
|
1626
|
+
} else {
|
|
1627
|
+
finish(null, "no_greeting");
|
|
1628
|
+
}
|
|
1629
|
+
break;
|
|
1630
|
+
case SMTPStep.ehlo:
|
|
1631
|
+
if (code.startsWith("250")) {
|
|
1632
|
+
const hasSTARTTLS = activeSequence.steps.includes(SMTPStep.startTls);
|
|
1633
|
+
if (!isTLS && useTLS && supportsSTARTTLS && !implicitTLS && hasSTARTTLS) {
|
|
1634
|
+
currentStepIndex = activeSequence.steps.indexOf(SMTPStep.startTls);
|
|
1635
|
+
executeStep(SMTPStep.startTls);
|
|
1636
|
+
} else {
|
|
1637
|
+
nextStep();
|
|
1638
|
+
}
|
|
1639
|
+
} else {
|
|
1640
|
+
finish(null, "ehlo_failed");
|
|
1641
|
+
}
|
|
1642
|
+
break;
|
|
1643
|
+
case SMTPStep.helo:
|
|
1644
|
+
if (code.startsWith("250")) {
|
|
1645
|
+
nextStep();
|
|
1646
|
+
} else {
|
|
1647
|
+
finish(null, "helo_failed");
|
|
1648
|
+
}
|
|
1649
|
+
break;
|
|
1650
|
+
case SMTPStep.startTls:
|
|
1651
|
+
if (code.startsWith("220")) {
|
|
1652
|
+
const plainSocket = socket;
|
|
1653
|
+
socket = tls.connect({
|
|
1654
|
+
...tlsOptions,
|
|
1655
|
+
socket: plainSocket,
|
|
1656
|
+
servername: isIPAddress(mxHost) ? void 0 : mxHost
|
|
1657
|
+
}, () => {
|
|
1658
|
+
isTLS = true;
|
|
1659
|
+
log("TLS upgraded");
|
|
1660
|
+
buffer = "";
|
|
1661
|
+
const starttlsIndex = activeSequence.steps.indexOf(SMTPStep.startTls);
|
|
1662
|
+
currentStepIndex = starttlsIndex;
|
|
1663
|
+
nextStep();
|
|
1664
|
+
});
|
|
1665
|
+
socket.on("data", handleData);
|
|
1666
|
+
socket.on("error", () => finish(null, "tls_error"));
|
|
1667
|
+
} else {
|
|
1668
|
+
nextStep();
|
|
1669
|
+
}
|
|
1670
|
+
break;
|
|
1671
|
+
case SMTPStep.mailFrom:
|
|
1672
|
+
if (code.startsWith("250")) {
|
|
1673
|
+
nextStep();
|
|
1674
|
+
} else {
|
|
1675
|
+
finish(null, "mail_from_rejected");
|
|
1676
|
+
}
|
|
1677
|
+
break;
|
|
1678
|
+
case SMTPStep.rcptTo:
|
|
1679
|
+
if (code.startsWith("250") || code.startsWith("251")) {
|
|
1680
|
+
finish(true, "valid");
|
|
1681
|
+
} else if (code.startsWith("552") || code.startsWith("452")) {
|
|
1682
|
+
finish(false, "over_quota");
|
|
1683
|
+
} else if (code.startsWith("4")) {
|
|
1684
|
+
finish(null, "temporary_failure");
|
|
1685
|
+
} else if (useVRFY && supportsVRFY && code.startsWith("5") && activeSequence.steps.includes(SMTPStep.vrfy)) {
|
|
1686
|
+
currentStepIndex = activeSequence.steps.indexOf(SMTPStep.vrfy);
|
|
1687
|
+
executeStep(SMTPStep.vrfy);
|
|
1688
|
+
} else {
|
|
1689
|
+
finish(null, "ambiguous");
|
|
1690
|
+
}
|
|
1691
|
+
break;
|
|
1692
|
+
case SMTPStep.vrfy:
|
|
1693
|
+
if (code.startsWith("250") || code.startsWith("252")) {
|
|
1694
|
+
finish(true, "vrfy_valid");
|
|
1695
|
+
} else if (code.startsWith("550")) {
|
|
1696
|
+
finish(false, "vrfy_invalid");
|
|
1697
|
+
} else {
|
|
1698
|
+
finish(null, "vrfy_failed");
|
|
1699
|
+
}
|
|
1700
|
+
break;
|
|
1701
|
+
case SMTPStep.quit:
|
|
1702
|
+
if (code.startsWith("221")) {
|
|
1703
|
+
finish(null, "quit_received");
|
|
1704
|
+
}
|
|
1705
|
+
break;
|
|
1706
|
+
}
|
|
1707
|
+
};
|
|
1708
|
+
let handleData = (data) => {
|
|
1709
|
+
if (resolved)
|
|
1710
|
+
return;
|
|
1711
|
+
buffer += data.toString();
|
|
1712
|
+
let pos;
|
|
1713
|
+
while ((pos = buffer.indexOf("\r\n")) !== -1) {
|
|
1714
|
+
const line = buffer.substring(0, pos);
|
|
1715
|
+
buffer = buffer.substring(pos + 2);
|
|
1716
|
+
processResponse(line.trim());
|
|
1717
|
+
}
|
|
1718
|
+
};
|
|
1719
|
+
if (port < 0 || port > 65535 || !Number.isInteger(port)) {
|
|
1720
|
+
finish(null, "invalid_port");
|
|
1721
|
+
return;
|
|
1722
|
+
}
|
|
1723
|
+
if (implicitTLS) {
|
|
1724
|
+
const connectOptions = {
|
|
1725
|
+
...tlsOptions,
|
|
1726
|
+
port,
|
|
1727
|
+
servername: isIPAddress(mxHost) ? void 0 : mxHost
|
|
1728
|
+
};
|
|
1729
|
+
socket = tls.connect(connectOptions, () => {
|
|
1730
|
+
log(`Connected to ${mxHost}:${port} with TLS`);
|
|
1731
|
+
socket.on("data", handleData);
|
|
1732
|
+
});
|
|
1733
|
+
} else {
|
|
1734
|
+
socket = net.connect({ host: mxHost, port }, () => {
|
|
1735
|
+
log(`Connected to ${mxHost}:${port}`);
|
|
1736
|
+
socket.on("data", handleData);
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
if (activeSequence.steps.length === 0) {
|
|
1740
|
+
finish(true, "sequence_complete");
|
|
1741
|
+
return;
|
|
1742
|
+
}
|
|
1743
|
+
const firstStep = activeSequence.steps[0];
|
|
1744
|
+
let connectionTimeout;
|
|
1745
|
+
let stepTimeout;
|
|
1746
|
+
const resetActivityTimeout = () => {
|
|
1747
|
+
if (stepTimeout) {
|
|
1748
|
+
clearTimeout(stepTimeout);
|
|
1749
|
+
}
|
|
1750
|
+
stepTimeout = setTimeout(() => {
|
|
1751
|
+
if (!resolved) {
|
|
1752
|
+
log(`Step timeout after ${timeout}ms of inactivity`);
|
|
1753
|
+
finish(null, "step_timeout");
|
|
1754
|
+
}
|
|
1755
|
+
}, timeout);
|
|
1756
|
+
};
|
|
1757
|
+
connectionTimeout = setTimeout(() => {
|
|
1758
|
+
if (!resolved) {
|
|
1759
|
+
log(`Connection timeout after ${timeout}ms`);
|
|
1760
|
+
finish(null, "connection_timeout");
|
|
1761
|
+
}
|
|
1762
|
+
}, timeout);
|
|
1763
|
+
if (firstStep !== SMTPStep.greeting) {
|
|
1764
|
+
executeStep(firstStep);
|
|
1765
|
+
}
|
|
1766
|
+
socket.setTimeout(timeout, () => {
|
|
1234
1767
|
if (!resolved) {
|
|
1235
|
-
log(
|
|
1236
|
-
|
|
1768
|
+
log(`Socket timeout after ${timeout}ms`);
|
|
1769
|
+
finish(null, "socket_timeout");
|
|
1237
1770
|
}
|
|
1238
1771
|
});
|
|
1239
|
-
socket.on("
|
|
1240
|
-
log(
|
|
1241
|
-
|
|
1772
|
+
socket.on("error", (error) => {
|
|
1773
|
+
log(`Socket error: ${error.message}`);
|
|
1774
|
+
if (!resolved)
|
|
1775
|
+
finish(null, "connection_error");
|
|
1242
1776
|
});
|
|
1243
|
-
|
|
1244
|
-
log(`[verifyMailboxSMTP] timed out (${timeout} ms)`);
|
|
1777
|
+
socket.on("close", () => {
|
|
1245
1778
|
if (!resolved) {
|
|
1246
|
-
|
|
1247
|
-
|
|
1779
|
+
log("Socket closed unexpectedly");
|
|
1780
|
+
finish(null, "connection_closed");
|
|
1248
1781
|
}
|
|
1249
|
-
}
|
|
1782
|
+
});
|
|
1783
|
+
const originalHandleData = handleData;
|
|
1784
|
+
handleData = (data) => {
|
|
1785
|
+
resetActivityTimeout();
|
|
1786
|
+
originalHandleData(data);
|
|
1787
|
+
};
|
|
1788
|
+
const enhancedCleanup = () => {
|
|
1789
|
+
if (resolved)
|
|
1790
|
+
return;
|
|
1791
|
+
resolved = true;
|
|
1792
|
+
if (connectionTimeout)
|
|
1793
|
+
clearTimeout(connectionTimeout);
|
|
1794
|
+
if (stepTimeout)
|
|
1795
|
+
clearTimeout(stepTimeout);
|
|
1796
|
+
socket.setTimeout(0);
|
|
1797
|
+
try {
|
|
1798
|
+
socket === null || socket === void 0 ? void 0 : socket.write("QUIT\r\n");
|
|
1799
|
+
} catch {
|
|
1800
|
+
}
|
|
1801
|
+
setTimeout(() => socket === null || socket === void 0 ? void 0 : socket.destroy(), 100);
|
|
1802
|
+
};
|
|
1803
|
+
cleanup = enhancedCleanup;
|
|
1250
1804
|
});
|
|
1251
1805
|
}
|
|
1252
1806
|
|
|
1253
|
-
var VerificationErrorCode;
|
|
1254
|
-
(function(VerificationErrorCode2) {
|
|
1255
|
-
VerificationErrorCode2["INVALID_FORMAT"] = "INVALID_FORMAT";
|
|
1256
|
-
VerificationErrorCode2["INVALID_DOMAIN"] = "INVALID_DOMAIN";
|
|
1257
|
-
VerificationErrorCode2["NO_MX_RECORDS"] = "NO_MX_RECORDS";
|
|
1258
|
-
VerificationErrorCode2["SMTP_CONNECTION_FAILED"] = "SMTP_CONNECTION_FAILED";
|
|
1259
|
-
VerificationErrorCode2["SMTP_TIMEOUT"] = "SMTP_TIMEOUT";
|
|
1260
|
-
VerificationErrorCode2["MAILBOX_NOT_FOUND"] = "MAILBOX_NOT_FOUND";
|
|
1261
|
-
VerificationErrorCode2["MAILBOX_FULL"] = "MAILBOX_FULL";
|
|
1262
|
-
VerificationErrorCode2["NETWORK_ERROR"] = "NETWORK_ERROR";
|
|
1263
|
-
VerificationErrorCode2["DISPOSABLE_EMAIL"] = "DISPOSABLE_EMAIL";
|
|
1264
|
-
VerificationErrorCode2["FREE_EMAIL_PROVIDER"] = "FREE_EMAIL_PROVIDER";
|
|
1265
|
-
})(VerificationErrorCode || (VerificationErrorCode = {}));
|
|
1266
|
-
|
|
1267
|
-
async function isValidEmailDomain(emailOrDomain, cache) {
|
|
1268
|
-
let [_, emailDomain] = (emailOrDomain === null || emailOrDomain === void 0 ? void 0 : emailOrDomain.split("@")) || [];
|
|
1269
|
-
if (!emailDomain) {
|
|
1270
|
-
emailDomain = _;
|
|
1271
|
-
}
|
|
1272
|
-
if (!emailDomain) {
|
|
1273
|
-
return false;
|
|
1274
|
-
}
|
|
1275
|
-
const cacheStore = getCacheStore(cache, "domainValid");
|
|
1276
|
-
const cached = await cacheStore.get(emailDomain);
|
|
1277
|
-
if (cached !== null && cached !== void 0) {
|
|
1278
|
-
return cached;
|
|
1279
|
-
}
|
|
1280
|
-
try {
|
|
1281
|
-
const result = isValid(emailDomain) || false;
|
|
1282
|
-
await cacheStore.set(emailDomain, result);
|
|
1283
|
-
return result;
|
|
1284
|
-
} catch (_e) {
|
|
1285
|
-
await cacheStore.set(emailDomain, false);
|
|
1286
|
-
return false;
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
function isValidEmail(emailAddress) {
|
|
1290
|
-
if (!emailAddress || typeof emailAddress !== "string") {
|
|
1291
|
-
return false;
|
|
1292
|
-
}
|
|
1293
|
-
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,}))$/;
|
|
1294
|
-
const emailLower = emailAddress.toLowerCase();
|
|
1295
|
-
if (emailLower.indexOf(".+") !== -1)
|
|
1296
|
-
return false;
|
|
1297
|
-
if (emailLower.indexOf("..") !== -1)
|
|
1298
|
-
return false;
|
|
1299
|
-
if (emailLower.startsWith(".") || emailLower.endsWith("."))
|
|
1300
|
-
return false;
|
|
1301
|
-
const parts = emailAddress.split("@");
|
|
1302
|
-
if (parts.length !== 2)
|
|
1303
|
-
return false;
|
|
1304
|
-
const [localPart, domain] = parts;
|
|
1305
|
-
if (!localPart || !domain)
|
|
1306
|
-
return false;
|
|
1307
|
-
if (localPart.length > 64)
|
|
1308
|
-
return false;
|
|
1309
|
-
if (domain.length > 253)
|
|
1310
|
-
return false;
|
|
1311
|
-
return re.test(emailLower);
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
1807
|
const defaultRegex = {
|
|
1315
1808
|
domainName: "Domain Name: *([^\\s]+)",
|
|
1316
1809
|
registrar: "Registrar: *(.+)",
|
|
@@ -1728,7 +2221,7 @@ function parseWhoisData({ rawData, domain }) {
|
|
|
1728
2221
|
return result;
|
|
1729
2222
|
}
|
|
1730
2223
|
|
|
1731
|
-
const
|
|
2224
|
+
const whoisServers = {
|
|
1732
2225
|
com: "whois.verisign-grs.com",
|
|
1733
2226
|
net: "whois.verisign-grs.com",
|
|
1734
2227
|
org: "whois.pir.org",
|
|
@@ -1828,7 +2321,7 @@ async function getWhoisData(domain, timeout = 5e3, debug = false, cache) {
|
|
|
1828
2321
|
throw new Error("Invalid domain");
|
|
1829
2322
|
}
|
|
1830
2323
|
log(`[whois] extracted TLD: ${tld} for domain: ${domain}`);
|
|
1831
|
-
const whoisServer =
|
|
2324
|
+
const whoisServer = whoisServers[tld];
|
|
1832
2325
|
if (!whoisServer) {
|
|
1833
2326
|
log(`[whois] no specific server for TLD ${tld}, trying IANA`);
|
|
1834
2327
|
const defaultServer = "whois.iana.org";
|
|
@@ -1854,8 +2347,8 @@ async function getWhoisData(domain, timeout = 5e3, debug = false, cache) {
|
|
|
1854
2347
|
await cacheStore.set(cacheKey, whoisData);
|
|
1855
2348
|
log(`[whois] successfully retrieved and cached WHOIS data for ${domain}`);
|
|
1856
2349
|
return whoisData;
|
|
1857
|
-
} catch (
|
|
1858
|
-
log(`[whois] failed to get WHOIS data for ${domain}: ${
|
|
2350
|
+
} catch (ignoredError) {
|
|
2351
|
+
log(`[whois] failed to get WHOIS data for ${domain}: ${ignoredError instanceof Error ? ignoredError.message : "Unknown error"}`);
|
|
1859
2352
|
return null;
|
|
1860
2353
|
}
|
|
1861
2354
|
}
|
|
@@ -1892,8 +2385,8 @@ async function getDomainAge(domain, timeout = 5e3, debug = false, cache) {
|
|
|
1892
2385
|
expirationDate: whoisData.expirationDate ? new Date(whoisData.expirationDate) : null,
|
|
1893
2386
|
updatedDate: whoisData.updatedDate ? new Date(whoisData.updatedDate) : null
|
|
1894
2387
|
};
|
|
1895
|
-
} catch (
|
|
1896
|
-
log(`[whois] error getting domain age for ${domain}: ${
|
|
2388
|
+
} catch (ignoredError) {
|
|
2389
|
+
log(`[whois] error getting domain age for ${domain}: ${ignoredError instanceof Error ? ignoredError.message : "Unknown error"}`);
|
|
1897
2390
|
return null;
|
|
1898
2391
|
}
|
|
1899
2392
|
}
|
|
@@ -1964,8 +2457,8 @@ async function getDomainRegistrationStatus(domain, timeout = 5e3, debug = false,
|
|
|
1964
2457
|
isPendingDelete,
|
|
1965
2458
|
isLocked
|
|
1966
2459
|
};
|
|
1967
|
-
} catch (
|
|
1968
|
-
log(`[whois] error getting domain registration status for ${domain}: ${
|
|
2460
|
+
} catch (ignoredError) {
|
|
2461
|
+
log(`[whois] error getting domain registration status for ${domain}: ${ignoredError instanceof Error ? ignoredError.message : "Unknown error"}`);
|
|
1969
2462
|
return null;
|
|
1970
2463
|
}
|
|
1971
2464
|
}
|
|
@@ -2141,7 +2634,7 @@ function createErrorResult(email, _error) {
|
|
|
2141
2634
|
metadata: {
|
|
2142
2635
|
verificationTime: 0,
|
|
2143
2636
|
cached: false,
|
|
2144
|
-
error: VerificationErrorCode.
|
|
2637
|
+
error: VerificationErrorCode.smtpConnectionFailed
|
|
2145
2638
|
}
|
|
2146
2639
|
};
|
|
2147
2640
|
}
|
|
@@ -2161,25 +2654,31 @@ async function isDisposableEmail(params) {
|
|
|
2161
2654
|
let cached;
|
|
2162
2655
|
try {
|
|
2163
2656
|
cached = await cacheStore.get(emailDomain);
|
|
2164
|
-
} catch (
|
|
2657
|
+
} catch (ignoredError) {
|
|
2165
2658
|
cached = null;
|
|
2166
2659
|
}
|
|
2167
2660
|
if (cached !== null && cached !== void 0) {
|
|
2168
|
-
log(`[isDisposableEmail] Cache hit for ${emailDomain}: ${cached}`);
|
|
2169
|
-
return cached;
|
|
2661
|
+
log(`[isDisposableEmail] Cache hit for ${emailDomain}: ${cached.isDisposable}`);
|
|
2662
|
+
return cached.isDisposable;
|
|
2170
2663
|
}
|
|
2171
2664
|
if (!disposableEmailProviders) {
|
|
2172
2665
|
disposableEmailProviders = new Set(require("./disposable-email-providers.json"));
|
|
2173
2666
|
}
|
|
2174
|
-
const
|
|
2667
|
+
const isDisposable = disposableEmailProviders.has(emailDomain);
|
|
2668
|
+
const richResult = {
|
|
2669
|
+
isDisposable,
|
|
2670
|
+
source: "disposable-email-providers.json",
|
|
2671
|
+
category: isDisposable ? "disposable" : void 0,
|
|
2672
|
+
checkedAt: Date.now()
|
|
2673
|
+
};
|
|
2175
2674
|
try {
|
|
2176
|
-
await cacheStore.set(emailDomain,
|
|
2177
|
-
log(`[isDisposableEmail] Cached result for ${emailDomain}: ${
|
|
2178
|
-
} catch (
|
|
2675
|
+
await cacheStore.set(emailDomain, richResult);
|
|
2676
|
+
log(`[isDisposableEmail] Cached result for ${emailDomain}: ${isDisposable}`);
|
|
2677
|
+
} catch (ignoredError) {
|
|
2179
2678
|
log(`[isDisposableEmail] Cache write error for ${emailDomain}`);
|
|
2180
2679
|
}
|
|
2181
|
-
log(`[isDisposableEmail] Check result for ${emailDomain}: ${
|
|
2182
|
-
return
|
|
2680
|
+
log(`[isDisposableEmail] Check result for ${emailDomain}: ${isDisposable}`);
|
|
2681
|
+
return isDisposable;
|
|
2183
2682
|
}
|
|
2184
2683
|
async function isFreeEmail(params) {
|
|
2185
2684
|
const { emailOrDomain, cache, logger } = params;
|
|
@@ -2194,25 +2693,30 @@ async function isFreeEmail(params) {
|
|
|
2194
2693
|
let cached;
|
|
2195
2694
|
try {
|
|
2196
2695
|
cached = await cacheStore.get(emailDomain);
|
|
2197
|
-
} catch (
|
|
2696
|
+
} catch (ignoredError) {
|
|
2198
2697
|
cached = null;
|
|
2199
2698
|
}
|
|
2200
2699
|
if (cached !== null && cached !== void 0) {
|
|
2201
|
-
log(`[isFreeEmail] Cache hit for ${emailDomain}: ${cached}`);
|
|
2202
|
-
return cached;
|
|
2700
|
+
log(`[isFreeEmail] Cache hit for ${emailDomain}: ${cached.isFree}`);
|
|
2701
|
+
return cached.isFree;
|
|
2203
2702
|
}
|
|
2204
2703
|
if (!freeEmailProviders) {
|
|
2205
2704
|
freeEmailProviders = new Set(require("./free-email-providers.json"));
|
|
2206
2705
|
}
|
|
2207
|
-
const
|
|
2706
|
+
const isFree = freeEmailProviders.has(emailDomain);
|
|
2707
|
+
const richResult = {
|
|
2708
|
+
isFree,
|
|
2709
|
+
provider: isFree ? emailDomain : void 0,
|
|
2710
|
+
checkedAt: Date.now()
|
|
2711
|
+
};
|
|
2208
2712
|
try {
|
|
2209
|
-
await cacheStore.set(emailDomain,
|
|
2210
|
-
log(`[isFreeEmail] Cached result for ${emailDomain}: ${
|
|
2211
|
-
} catch (
|
|
2713
|
+
await cacheStore.set(emailDomain, richResult);
|
|
2714
|
+
log(`[isFreeEmail] Cached result for ${emailDomain}: ${isFree}`);
|
|
2715
|
+
} catch (ignoredError) {
|
|
2212
2716
|
log(`[isFreeEmail] Cache write error for ${emailDomain}`);
|
|
2213
2717
|
}
|
|
2214
|
-
log(`[isFreeEmail] Check result for ${emailDomain}: ${
|
|
2215
|
-
return
|
|
2718
|
+
log(`[isFreeEmail] Check result for ${emailDomain}: ${isFree}`);
|
|
2719
|
+
return isFree;
|
|
2216
2720
|
}
|
|
2217
2721
|
const domainPorts = {
|
|
2218
2722
|
// 465 or 587
|
|
@@ -2220,7 +2724,7 @@ const domainPorts = {
|
|
|
2220
2724
|
"ovh.net": 465
|
|
2221
2725
|
};
|
|
2222
2726
|
async function verifyEmail(params) {
|
|
2223
|
-
var _a;
|
|
2727
|
+
var _a, _b;
|
|
2224
2728
|
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;
|
|
2225
2729
|
const startTime = Date.now();
|
|
2226
2730
|
const log = debug ? console.debug : (..._args) => {
|
|
@@ -2240,7 +2744,7 @@ async function verifyEmail(params) {
|
|
|
2240
2744
|
if (!isValidEmail(emailAddress)) {
|
|
2241
2745
|
if (result.metadata) {
|
|
2242
2746
|
result.metadata.verificationTime = Date.now() - startTime;
|
|
2243
|
-
result.metadata.error = VerificationErrorCode.
|
|
2747
|
+
result.metadata.error = VerificationErrorCode.invalidFormat;
|
|
2244
2748
|
}
|
|
2245
2749
|
return result;
|
|
2246
2750
|
}
|
|
@@ -2266,14 +2770,14 @@ async function verifyEmail(params) {
|
|
|
2266
2770
|
if (!domain || !local) {
|
|
2267
2771
|
if (result.metadata) {
|
|
2268
2772
|
result.metadata.verificationTime = Date.now() - startTime;
|
|
2269
|
-
result.metadata.error = VerificationErrorCode.
|
|
2773
|
+
result.metadata.error = VerificationErrorCode.invalidFormat;
|
|
2270
2774
|
}
|
|
2271
2775
|
return result;
|
|
2272
2776
|
}
|
|
2273
2777
|
if (!await isValidEmailDomain(domain, params.cache)) {
|
|
2274
2778
|
if (result.metadata) {
|
|
2275
2779
|
result.metadata.verificationTime = Date.now() - startTime;
|
|
2276
|
-
result.metadata.error = VerificationErrorCode.
|
|
2780
|
+
result.metadata.error = VerificationErrorCode.invalidDomain;
|
|
2277
2781
|
}
|
|
2278
2782
|
return result;
|
|
2279
2783
|
}
|
|
@@ -2282,7 +2786,7 @@ async function verifyEmail(params) {
|
|
|
2282
2786
|
result.isDisposable = await isDisposableEmail({ emailOrDomain: emailAddress, cache: params.cache, logger: log });
|
|
2283
2787
|
log(`[verifyEmail] Disposable check result: ${result.isDisposable}`);
|
|
2284
2788
|
if (result.isDisposable && result.metadata) {
|
|
2285
|
-
result.metadata.error = VerificationErrorCode.
|
|
2789
|
+
result.metadata.error = VerificationErrorCode.disposableEmail;
|
|
2286
2790
|
}
|
|
2287
2791
|
}
|
|
2288
2792
|
if (checkFree) {
|
|
@@ -2303,8 +2807,8 @@ async function verifyEmail(params) {
|
|
|
2303
2807
|
try {
|
|
2304
2808
|
result.domainAge = await getDomainAge(domain, whoisTimeout, debug, params.cache);
|
|
2305
2809
|
log(`[verifyEmail] Domain age result:`, result.domainAge ? `${result.domainAge.ageInDays} days` : "null");
|
|
2306
|
-
} catch (
|
|
2307
|
-
log("[verifyEmail] Failed to get domain age",
|
|
2810
|
+
} catch (error) {
|
|
2811
|
+
log("[verifyEmail] Failed to get domain age", error);
|
|
2308
2812
|
result.domainAge = null;
|
|
2309
2813
|
}
|
|
2310
2814
|
} else if (checkDomainAge && shouldSkipDomainWhois) {
|
|
@@ -2315,8 +2819,8 @@ async function verifyEmail(params) {
|
|
|
2315
2819
|
try {
|
|
2316
2820
|
result.domainRegistration = await getDomainRegistrationStatus(domain, whoisTimeout, debug, params.cache);
|
|
2317
2821
|
log(`[verifyEmail] Domain registration result:`, ((_a = result.domainRegistration) === null || _a === void 0 ? void 0 : _a.isRegistered) ? "registered" : "not registered");
|
|
2318
|
-
} catch (
|
|
2319
|
-
log("[verifyEmail] Failed to get domain registration status",
|
|
2822
|
+
} catch (error) {
|
|
2823
|
+
log("[verifyEmail] Failed to get domain registration status", error);
|
|
2320
2824
|
result.domainRegistration = null;
|
|
2321
2825
|
}
|
|
2322
2826
|
} else if (checkDomainRegistration && shouldSkipDomainWhois) {
|
|
@@ -2329,14 +2833,14 @@ async function verifyEmail(params) {
|
|
|
2329
2833
|
result.validMx = mxRecords.length > 0;
|
|
2330
2834
|
log(`[verifyEmail] MX records found: ${mxRecords.length}, valid: ${result.validMx}`);
|
|
2331
2835
|
if (!result.validMx && result.metadata) {
|
|
2332
|
-
result.metadata.error = VerificationErrorCode.
|
|
2836
|
+
result.metadata.error = VerificationErrorCode.noMxRecords;
|
|
2333
2837
|
}
|
|
2334
2838
|
if (verifySmtp && mxRecords.length > 0) {
|
|
2335
2839
|
const cacheKey = `${emailAddress}:smtp`;
|
|
2336
2840
|
const smtpCacheInstance = getCacheStore(params.cache, "smtp");
|
|
2337
2841
|
const cachedSmtp = await smtpCacheInstance.get(cacheKey);
|
|
2338
2842
|
if (cachedSmtp !== null && cachedSmtp !== void 0) {
|
|
2339
|
-
result.validSmtp = cachedSmtp;
|
|
2843
|
+
result.validSmtp = (_b = cachedSmtp.isDeliverable) !== null && _b !== void 0 ? _b : null;
|
|
2340
2844
|
log(`[verifyEmail] SMTP result from cache: ${result.validSmtp} for ${emailAddress}`);
|
|
2341
2845
|
if (result.metadata) {
|
|
2342
2846
|
result.metadata.cached = true;
|
|
@@ -2356,30 +2860,39 @@ async function verifyEmail(params) {
|
|
|
2356
2860
|
domainPort = domainPorts[mxDomain.domain];
|
|
2357
2861
|
}
|
|
2358
2862
|
}
|
|
2359
|
-
const smtpResult = await verifyMailboxSMTP({
|
|
2863
|
+
const { smtpResult, cached, port } = await verifyMailboxSMTP({
|
|
2360
2864
|
local,
|
|
2361
2865
|
domain,
|
|
2362
2866
|
mxRecords,
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2867
|
+
options: {
|
|
2868
|
+
cache: params.cache,
|
|
2869
|
+
ports: domainPort ? [domainPort] : void 0,
|
|
2870
|
+
timeout,
|
|
2871
|
+
debug,
|
|
2872
|
+
maxRetries: params.retryAttempts
|
|
2873
|
+
}
|
|
2367
2874
|
});
|
|
2368
2875
|
await smtpCacheInstance.set(cacheKey, smtpResult);
|
|
2369
|
-
|
|
2876
|
+
if (!smtpResult.canConnectSmtp) {
|
|
2877
|
+
result.validSmtp = null;
|
|
2878
|
+
} else {
|
|
2879
|
+
result.validSmtp = smtpResult.isDeliverable;
|
|
2880
|
+
}
|
|
2881
|
+
if (result.metadata)
|
|
2882
|
+
result.metadata.cached = cached;
|
|
2370
2883
|
log(`[verifyEmail] SMTP verification result: ${result.validSmtp} for ${emailAddress} (cached for future use)`);
|
|
2371
2884
|
}
|
|
2372
2885
|
if (result.validSmtp === false && result.metadata) {
|
|
2373
|
-
result.metadata.error = VerificationErrorCode.
|
|
2886
|
+
result.metadata.error = VerificationErrorCode.mailboxNotFound;
|
|
2374
2887
|
} else if (result.validSmtp === null && result.metadata) {
|
|
2375
|
-
result.metadata.error = VerificationErrorCode.
|
|
2888
|
+
result.metadata.error = VerificationErrorCode.smtpConnectionFailed;
|
|
2376
2889
|
}
|
|
2377
2890
|
}
|
|
2378
|
-
} catch (
|
|
2379
|
-
log("[verifyEmail] Failed to resolve MX records",
|
|
2891
|
+
} catch (error) {
|
|
2892
|
+
log("[verifyEmail] Failed to resolve MX records", error);
|
|
2380
2893
|
result.validMx = false;
|
|
2381
2894
|
if (result.metadata) {
|
|
2382
|
-
result.metadata.error = VerificationErrorCode.
|
|
2895
|
+
result.metadata.error = VerificationErrorCode.noMxRecords;
|
|
2383
2896
|
}
|
|
2384
2897
|
}
|
|
2385
2898
|
} else if ((verifyMx || verifySmtp) && shouldSkipMx) {
|
|
@@ -2391,5 +2904,5 @@ async function verifyEmail(params) {
|
|
|
2391
2904
|
return result;
|
|
2392
2905
|
}
|
|
2393
2906
|
|
|
2394
|
-
export {
|
|
2907
|
+
export { DEFAULT_CACHE_OPTIONS, EmailProvider, LRUAdapter, RedisAdapter, SMTPStep, VerificationErrorCode, cleanNameForAlgorithm, clearDefaultCache, commonEmailDomains, defaultDomainSuggestionMethod, defaultNameDetectionMethod, detectName, detectNameForAlgorithm, detectNameFromEmail, domainPorts, getCacheStore, getDefaultCache, getDomainAge, getDomainRegistrationStatus, getDomainSimilarity, isCommonDomain, isDisposableEmail, isFreeEmail, isValidEmail, isValidEmailDomain, parseSmtpError, resetDefaultCache, suggestDomain, suggestEmailDomain, verifyEmail, verifyEmailBatch };
|
|
2395
2908
|
//# sourceMappingURL=index.esm.js.map
|