@emailcheck/email-validator-js 3.4.4 → 4.0.0-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 +3 -3
- package/dist/cache-interface.d.ts +4 -2
- package/dist/cli/index.js +273 -105
- package/dist/cli/index.js.map +1 -1
- package/dist/index.esm.js +280 -98
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +280 -98
- package/dist/index.js.map +1 -1
- 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/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.esm.js.map +1 -1
- package/dist/smtp-verifier.d.ts +31 -14
- package/dist/types.d.ts +81 -26
- package/package.json +12 -2
package/dist/index.esm.js
CHANGED
|
@@ -2,6 +2,7 @@ import { lru } from 'tiny-lru';
|
|
|
2
2
|
import { isValid, parse } from 'psl';
|
|
3
3
|
import { stringSimilarity } from 'string-similarity-js';
|
|
4
4
|
import { promises } from 'node:dns';
|
|
5
|
+
import { randomBytes } from 'node:crypto';
|
|
5
6
|
import * as net$1 from 'node:net';
|
|
6
7
|
import * as tls from 'node:tls';
|
|
7
8
|
|
|
@@ -658,9 +659,9 @@ function parseCompositeNamePart(part) {
|
|
|
658
659
|
cleaned = pureAlpha;
|
|
659
660
|
confidence = 0.6;
|
|
660
661
|
} else {
|
|
661
|
-
const
|
|
662
|
-
if (
|
|
663
|
-
cleaned =
|
|
662
|
+
const baseMatch = part.match(/^([a-zA-Z]+)\d*$/);
|
|
663
|
+
if (baseMatch) {
|
|
664
|
+
cleaned = baseMatch[1];
|
|
664
665
|
confidence = 0.75;
|
|
665
666
|
} else {
|
|
666
667
|
cleaned = part;
|
|
@@ -674,9 +675,7 @@ function parseCompositeNamePart(part) {
|
|
|
674
675
|
confidence = Math.min(1, confidence + 0.2);
|
|
675
676
|
}
|
|
676
677
|
}
|
|
677
|
-
|
|
678
|
-
const base = baseMatch ? baseMatch[1] : part;
|
|
679
|
-
return { base, hasNumbers, cleaned, confidence };
|
|
678
|
+
return { hasNumbers, cleaned, confidence };
|
|
680
679
|
}
|
|
681
680
|
function isLikelyName(str, allowNumbers = false, allowSingleLetter = false) {
|
|
682
681
|
if (!str)
|
|
@@ -967,6 +966,9 @@ function parseDsn(reply) {
|
|
|
967
966
|
return null;
|
|
968
967
|
return { class: Number(match[1]), subject: Number(match[2]), detail: Number(match[3]) };
|
|
969
968
|
}
|
|
969
|
+
function dsnToString(dsn) {
|
|
970
|
+
return `${dsn.class}.${dsn.subject}.${dsn.detail}`;
|
|
971
|
+
}
|
|
970
972
|
function isPolicyBlock(reply) {
|
|
971
973
|
const dsn = parseDsn(reply);
|
|
972
974
|
return (dsn === null || dsn === void 0 ? void 0 : dsn.class) === 5 && (dsn === null || dsn === void 0 ? void 0 : dsn.subject) === 7;
|
|
@@ -987,8 +989,11 @@ function isInvalidMailboxError(reply) {
|
|
|
987
989
|
return false;
|
|
988
990
|
return true;
|
|
989
991
|
}
|
|
992
|
+
function defaultProbeLocal() {
|
|
993
|
+
return `${randomBytes(8).toString("hex")}-noexist`;
|
|
994
|
+
}
|
|
990
995
|
async function verifyMailboxSMTP(params) {
|
|
991
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
996
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
992
997
|
const { local, domain, options = {} } = params;
|
|
993
998
|
const mxRecords = (_a = params.mxRecords) !== null && _a !== void 0 ? _a : [];
|
|
994
999
|
const ports = ((_b = options.ports) !== null && _b !== void 0 ? _b : DEFAULT_PORTS).filter((port) => Number.isInteger(port) && port > 0 && port < 65536);
|
|
@@ -1001,16 +1006,29 @@ async function verifyMailboxSMTP(params) {
|
|
|
1001
1006
|
const cache = options.cache;
|
|
1002
1007
|
const log = debug ? (...args) => console.log("[SMTP]", ...args) : () => {
|
|
1003
1008
|
};
|
|
1004
|
-
const
|
|
1005
|
-
|
|
1009
|
+
const startedAtMs = Date.now();
|
|
1010
|
+
const primaryMx = mxRecords[0];
|
|
1011
|
+
if (!primaryMx) {
|
|
1006
1012
|
log("No MX records found");
|
|
1007
|
-
|
|
1013
|
+
const metrics2 = makeMetrics([], 0, 0, void 0, startedAtMs);
|
|
1014
|
+
return { smtpResult: failureResult("no_mx_records", metrics2), cached: false, port: 0, portCached: false };
|
|
1008
1015
|
}
|
|
1009
|
-
log(`Verifying ${local}@${domain} via ${
|
|
1016
|
+
log(`Verifying ${local}@${domain} via ${primaryMx} (mx count=${mxRecords.length})`);
|
|
1010
1017
|
const transcript = [];
|
|
1011
1018
|
const commands = [];
|
|
1019
|
+
const probeOptions = {
|
|
1020
|
+
local,
|
|
1021
|
+
domain,
|
|
1022
|
+
timeout,
|
|
1023
|
+
tlsConfig,
|
|
1024
|
+
hostname,
|
|
1025
|
+
sequence,
|
|
1026
|
+
log,
|
|
1027
|
+
catchAllProbeLocal: options.catchAllProbeLocal,
|
|
1028
|
+
pipelining: (_h = options.pipelining) !== null && _h !== void 0 ? _h : "auto"
|
|
1029
|
+
};
|
|
1012
1030
|
const verdictCache = cache ? getCacheStore(cache, "smtp") : null;
|
|
1013
|
-
const verdictKey = `${
|
|
1031
|
+
const verdictKey = `${primaryMx}:${local}@${domain}`;
|
|
1014
1032
|
if (verdictCache) {
|
|
1015
1033
|
const cachedResult = await safeCacheGet(verdictCache, verdictKey);
|
|
1016
1034
|
if (cachedResult) {
|
|
@@ -1019,81 +1037,99 @@ async function verifyMailboxSMTP(params) {
|
|
|
1019
1037
|
}
|
|
1020
1038
|
}
|
|
1021
1039
|
const portCache = cache ? getCacheStore(cache, "smtpPort") : null;
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
});
|
|
1037
|
-
collectTranscript(transcript, commands, probe,
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1040
|
+
const cachedPort = portCache ? await safeCacheGet(portCache, primaryMx) : null;
|
|
1041
|
+
const mxHostsTried = [];
|
|
1042
|
+
let mxAttempts = 0;
|
|
1043
|
+
let portAttempts = 0;
|
|
1044
|
+
let lastReason = "all_attempts_failed";
|
|
1045
|
+
let lastEnhancedStatus;
|
|
1046
|
+
let lastResponseCode;
|
|
1047
|
+
for (const mxHost of mxRecords) {
|
|
1048
|
+
mxHostsTried.push(mxHost);
|
|
1049
|
+
mxAttempts++;
|
|
1050
|
+
const portsForThisMx = mxHost === primaryMx && cachedPort ? [cachedPort, ...ports.filter((p) => p !== cachedPort)] : ports;
|
|
1051
|
+
for (const port of portsForThisMx) {
|
|
1052
|
+
portAttempts++;
|
|
1053
|
+
log(`Testing ${mxHost}:${port}`);
|
|
1054
|
+
const probe = await runProbe({ ...probeOptions, mxHost, port });
|
|
1055
|
+
collectTranscript(transcript, commands, probe, mxHost, port);
|
|
1056
|
+
lastReason = probe.reason;
|
|
1057
|
+
if (probe.enhancedStatus !== void 0)
|
|
1058
|
+
lastEnhancedStatus = probe.enhancedStatus;
|
|
1059
|
+
if (probe.responseCode !== void 0)
|
|
1060
|
+
lastResponseCode = probe.responseCode;
|
|
1061
|
+
if (probe.result !== null) {
|
|
1062
|
+
const metrics2 = makeMetrics(mxHostsTried, mxAttempts, portAttempts, mxHost, startedAtMs);
|
|
1063
|
+
const smtpResult2 = toSmtpVerificationResult(probe, {
|
|
1064
|
+
transcript: captureTranscript ? transcript : void 0,
|
|
1065
|
+
commands: captureTranscript ? commands : void 0,
|
|
1066
|
+
metrics: metrics2
|
|
1067
|
+
});
|
|
1068
|
+
await safeCacheSet(verdictCache, verdictKey, smtpResult2);
|
|
1069
|
+
if (mxHost === primaryMx)
|
|
1070
|
+
await safeCacheSet(portCache, primaryMx, port);
|
|
1071
|
+
return { smtpResult: smtpResult2, cached: false, port, portCached: cachedPort === port };
|
|
1072
|
+
}
|
|
1052
1073
|
}
|
|
1053
1074
|
}
|
|
1054
|
-
log(
|
|
1075
|
+
log(`All MX\xD7port attempts failed (mx=${mxAttempts}, port=${portAttempts})`);
|
|
1076
|
+
const metrics = makeMetrics(mxHostsTried, mxAttempts, portAttempts, void 0, startedAtMs);
|
|
1077
|
+
const smtpResult = {
|
|
1078
|
+
...failureResult(lastReason, metrics),
|
|
1079
|
+
...lastEnhancedStatus !== void 0 ? { enhancedStatus: lastEnhancedStatus } : {},
|
|
1080
|
+
...lastResponseCode !== void 0 ? { responseCode: lastResponseCode } : {},
|
|
1081
|
+
...captureTranscript ? { transcript: [...transcript], commands: [...commands] } : {}
|
|
1082
|
+
};
|
|
1083
|
+
return { smtpResult, cached: false, port: 0, portCached: false };
|
|
1084
|
+
}
|
|
1085
|
+
function makeMetrics(mxHostsTried, mxAttempts, portAttempts, mxHostUsed, startedAtMs) {
|
|
1055
1086
|
return {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
},
|
|
1060
|
-
|
|
1061
|
-
port: 0,
|
|
1062
|
-
portCached: false
|
|
1087
|
+
mxAttempts,
|
|
1088
|
+
portAttempts,
|
|
1089
|
+
mxHostsTried: [...mxHostsTried],
|
|
1090
|
+
...mxHostUsed !== void 0 ? { mxHostUsed } : {},
|
|
1091
|
+
totalDurationMs: Date.now() - startedAtMs
|
|
1063
1092
|
};
|
|
1064
1093
|
}
|
|
1065
|
-
function collectTranscript(transcript, commands, probe, port) {
|
|
1094
|
+
function collectTranscript(transcript, commands, probe, mxHost, port) {
|
|
1095
|
+
const prefix = `${mxHost}:${port}`;
|
|
1066
1096
|
for (const line of probe.transcript)
|
|
1067
|
-
transcript.push(`${
|
|
1097
|
+
transcript.push(`${prefix}|s| ${line}`);
|
|
1068
1098
|
for (const cmd of probe.commands)
|
|
1069
|
-
commands.push(`${
|
|
1099
|
+
commands.push(`${prefix}|c| ${cmd}`);
|
|
1070
1100
|
}
|
|
1071
|
-
function failureResult(
|
|
1101
|
+
function failureResult(reason, metrics) {
|
|
1072
1102
|
return {
|
|
1073
1103
|
canConnectSmtp: false,
|
|
1074
1104
|
hasFullInbox: false,
|
|
1075
1105
|
isCatchAll: false,
|
|
1076
1106
|
isDeliverable: false,
|
|
1077
1107
|
isDisabled: false,
|
|
1078
|
-
error,
|
|
1079
|
-
|
|
1080
|
-
|
|
1108
|
+
error: reason,
|
|
1109
|
+
checkedAt: Date.now(),
|
|
1110
|
+
metrics
|
|
1081
1111
|
};
|
|
1082
1112
|
}
|
|
1083
|
-
function toSmtpVerificationResult(
|
|
1084
|
-
|
|
1113
|
+
function toSmtpVerificationResult(probe, extras) {
|
|
1114
|
+
var _a;
|
|
1115
|
+
const result = probe.result;
|
|
1116
|
+
const out = {
|
|
1085
1117
|
canConnectSmtp: result !== null,
|
|
1086
|
-
hasFullInbox:
|
|
1087
|
-
isCatchAll: false,
|
|
1118
|
+
hasFullInbox: probe.reason === "over_quota",
|
|
1119
|
+
isCatchAll: (_a = probe.isCatchAll) !== null && _a !== void 0 ? _a : false,
|
|
1088
1120
|
isDeliverable: result === true,
|
|
1089
1121
|
isDisabled: result === false,
|
|
1090
|
-
error: result === true ? void 0 :
|
|
1091
|
-
|
|
1092
|
-
|
|
1122
|
+
error: result === true ? void 0 : probe.reason,
|
|
1123
|
+
checkedAt: Date.now(),
|
|
1124
|
+
metrics: extras.metrics,
|
|
1125
|
+
...probe.enhancedStatus !== void 0 ? { enhancedStatus: probe.enhancedStatus } : {},
|
|
1126
|
+
...probe.responseCode !== void 0 ? { responseCode: probe.responseCode } : {}
|
|
1093
1127
|
};
|
|
1094
|
-
if (
|
|
1095
|
-
|
|
1096
|
-
|
|
1128
|
+
if (extras.transcript)
|
|
1129
|
+
out.transcript = [...extras.transcript];
|
|
1130
|
+
if (extras.commands)
|
|
1131
|
+
out.commands = [...extras.commands];
|
|
1132
|
+
return out;
|
|
1097
1133
|
}
|
|
1098
1134
|
async function safeCacheGet(store, key) {
|
|
1099
1135
|
if (!store)
|
|
@@ -1125,6 +1161,12 @@ class SMTPProbeConnection {
|
|
|
1125
1161
|
this.currentStepIndex = 0;
|
|
1126
1162
|
this.transcript = [];
|
|
1127
1163
|
this.commands = [];
|
|
1164
|
+
this.supportsPipelining = false;
|
|
1165
|
+
this.dualPhase = "idle";
|
|
1166
|
+
this.realOutcome = "pending";
|
|
1167
|
+
this.probeOutcome = "pending";
|
|
1168
|
+
this.dualPipelined = false;
|
|
1169
|
+
this.pendingDecision = null;
|
|
1128
1170
|
this.onData = (data) => {
|
|
1129
1171
|
if (this.resolved)
|
|
1130
1172
|
return;
|
|
@@ -1148,6 +1190,7 @@ class SMTPProbeConnection {
|
|
|
1148
1190
|
minVersion: "TLSv1.2",
|
|
1149
1191
|
...typeof p.tlsConfig === "object" ? p.tlsConfig : {}
|
|
1150
1192
|
};
|
|
1193
|
+
this.probeLocal = p.catchAllProbeLocal ? p.catchAllProbeLocal(p.local, p.domain) : defaultProbeLocal();
|
|
1151
1194
|
}
|
|
1152
1195
|
run() {
|
|
1153
1196
|
return new Promise((resolve) => {
|
|
@@ -1208,6 +1251,7 @@ class SMTPProbeConnection {
|
|
|
1208
1251
|
switch (step) {
|
|
1209
1252
|
case SMTPStep.greeting:
|
|
1210
1253
|
return;
|
|
1254
|
+
// server-driven; nothing to send
|
|
1211
1255
|
case SMTPStep.ehlo:
|
|
1212
1256
|
this.send(`EHLO ${this.p.hostname}`);
|
|
1213
1257
|
return;
|
|
@@ -1220,39 +1264,87 @@ class SMTPProbeConnection {
|
|
|
1220
1264
|
return;
|
|
1221
1265
|
}
|
|
1222
1266
|
case SMTPStep.rcptTo:
|
|
1223
|
-
this.
|
|
1267
|
+
this.executeEnvelope();
|
|
1224
1268
|
return;
|
|
1225
1269
|
}
|
|
1226
1270
|
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Send the dual-probe envelope (real RCPT + probe RCPT + RSET). Pipelined
|
|
1273
|
+
* when the MX advertised PIPELINING (or `pipelining: 'force'`); sequential
|
|
1274
|
+
* otherwise.
|
|
1275
|
+
*/
|
|
1276
|
+
executeEnvelope() {
|
|
1277
|
+
var _a;
|
|
1278
|
+
const wantsPipelining = this.p.pipelining === "force" || this.p.pipelining === "auto" && this.supportsPipelining;
|
|
1279
|
+
const realCmd = `RCPT TO:<${this.p.local}@${this.p.domain}>`;
|
|
1280
|
+
if (wantsPipelining) {
|
|
1281
|
+
this.dualPipelined = true;
|
|
1282
|
+
const probeCmd = `RCPT TO:<${this.probeLocal}@${this.p.domain}>`;
|
|
1283
|
+
const rsetCmd = "RSET";
|
|
1284
|
+
this.commands.push(realCmd, probeCmd, rsetCmd);
|
|
1285
|
+
this.p.log(`\u2192 ${realCmd}`);
|
|
1286
|
+
this.p.log(`\u2192 ${probeCmd}`);
|
|
1287
|
+
this.p.log(`\u2192 ${rsetCmd}`);
|
|
1288
|
+
(_a = this.socket) === null || _a === void 0 ? void 0 : _a.write(`${realCmd}\r
|
|
1289
|
+
${probeCmd}\r
|
|
1290
|
+
${rsetCmd}\r
|
|
1291
|
+
`);
|
|
1292
|
+
this.dualPhase = "rcpt_real";
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
this.dualPipelined = false;
|
|
1296
|
+
this.send(realCmd);
|
|
1297
|
+
this.dualPhase = "rcpt_real";
|
|
1298
|
+
}
|
|
1227
1299
|
processLine(line) {
|
|
1228
1300
|
if (this.resolved)
|
|
1229
1301
|
return;
|
|
1230
1302
|
this.transcript.push(line);
|
|
1231
1303
|
this.p.log(`\u2190 ${line}`);
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1304
|
+
const codeStr = line.slice(0, 3);
|
|
1305
|
+
const numericCode = /^\d{3}$/.test(codeStr) ? parseInt(codeStr, 10) : null;
|
|
1306
|
+
if (numericCode !== null)
|
|
1307
|
+
this.lastResponseCode = numericCode;
|
|
1308
|
+
const dsn = parseDsn(line);
|
|
1309
|
+
if (dsn)
|
|
1310
|
+
this.lastEnhancedStatus = dsnToString(dsn);
|
|
1311
|
+
if (this.dualPhase === "idle" || this.dualPhase === "rcpt_real") {
|
|
1312
|
+
if (isHighVolume(line)) {
|
|
1313
|
+
this.finish(true, "high_volume");
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
if (isOverQuota(line)) {
|
|
1317
|
+
this.isCatchAllFlag = false;
|
|
1318
|
+
this.finish(false, "over_quota");
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
if (isInvalidMailboxError(line)) {
|
|
1322
|
+
this.isCatchAllFlag = false;
|
|
1323
|
+
this.finish(false, "not_found");
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1239
1326
|
}
|
|
1240
|
-
if (
|
|
1241
|
-
this.
|
|
1327
|
+
if (MULTILINE_RE.test(line)) {
|
|
1328
|
+
const step = this.steps[this.currentStepIndex];
|
|
1329
|
+
if ((step === SMTPStep.ehlo || step === SMTPStep.helo) && line.startsWith("250-")) {
|
|
1330
|
+
const upper = line.toUpperCase();
|
|
1331
|
+
if (upper.includes("PIPELINING"))
|
|
1332
|
+
this.supportsPipelining = true;
|
|
1333
|
+
}
|
|
1242
1334
|
return;
|
|
1243
1335
|
}
|
|
1244
|
-
if (MULTILINE_RE.test(line))
|
|
1245
|
-
return;
|
|
1246
|
-
const code = line.slice(0, 3);
|
|
1247
|
-
const numericCode = /^\d{3}$/.test(code) ? parseInt(code, 10) : null;
|
|
1248
1336
|
if (numericCode === null) {
|
|
1249
1337
|
this.finish(null, "unrecognized_response");
|
|
1250
1338
|
return;
|
|
1251
1339
|
}
|
|
1252
|
-
this.dispatch(numericCode);
|
|
1340
|
+
this.dispatch(numericCode, line);
|
|
1253
1341
|
}
|
|
1254
|
-
dispatch(code) {
|
|
1342
|
+
dispatch(code, line) {
|
|
1255
1343
|
const step = this.steps[this.currentStepIndex];
|
|
1344
|
+
if (this.dualPhase !== "idle" && step === SMTPStep.rcptTo) {
|
|
1345
|
+
this.handleEnvelopeReply(code, line);
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1256
1348
|
switch (step) {
|
|
1257
1349
|
case SMTPStep.greeting:
|
|
1258
1350
|
if (code === 220)
|
|
@@ -1279,17 +1371,93 @@ class SMTPProbeConnection {
|
|
|
1279
1371
|
this.finish(null, "mail_from_rejected");
|
|
1280
1372
|
return;
|
|
1281
1373
|
case SMTPStep.rcptTo:
|
|
1282
|
-
|
|
1283
|
-
this.finish(true, "valid");
|
|
1284
|
-
else if (code === 552 || code === 452)
|
|
1285
|
-
this.finish(false, "over_quota");
|
|
1286
|
-
else if (code >= 400 && code < 500)
|
|
1287
|
-
this.finish(null, "temporary_failure");
|
|
1288
|
-
else
|
|
1289
|
-
this.finish(null, "ambiguous");
|
|
1374
|
+
this.handleEnvelopeReply(code, line);
|
|
1290
1375
|
return;
|
|
1291
1376
|
}
|
|
1292
1377
|
}
|
|
1378
|
+
/**
|
|
1379
|
+
* Dual-probe / pipelined-envelope reply router. Demuxes server replies for
|
|
1380
|
+
* the three queued commands (real RCPT, probe RCPT, RSET) and resolves
|
|
1381
|
+
* with the catch-all-aware verdict.
|
|
1382
|
+
*/
|
|
1383
|
+
handleEnvelopeReply(code, line) {
|
|
1384
|
+
if (this.dualPhase === "rcpt_real") {
|
|
1385
|
+
this.realOutcome = classifyRcpt(code);
|
|
1386
|
+
if (code === 552 || code === 452 || isOverQuota(line)) {
|
|
1387
|
+
this.isCatchAllFlag = false;
|
|
1388
|
+
if (this.dualPipelined) {
|
|
1389
|
+
this.pendingDecision = { result: false, reason: "over_quota" };
|
|
1390
|
+
this.dualPhase = "rcpt_probe";
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1393
|
+
this.finish(false, "over_quota");
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
if (this.realOutcome === "soft_reject") {
|
|
1397
|
+
if (this.dualPipelined) {
|
|
1398
|
+
this.pendingDecision = { result: null, reason: "temporary_failure" };
|
|
1399
|
+
this.dualPhase = "rcpt_probe";
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
this.finish(null, "temporary_failure");
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
if (this.realOutcome === "hard_reject") {
|
|
1406
|
+
const reason = isInvalidMailboxError(line) ? "not_found" : "ambiguous";
|
|
1407
|
+
const result = reason === "not_found" ? false : null;
|
|
1408
|
+
this.isCatchAllFlag = false;
|
|
1409
|
+
if (this.dualPipelined) {
|
|
1410
|
+
this.pendingDecision = { result, reason };
|
|
1411
|
+
this.dualPhase = "rcpt_probe";
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
this.finish(result, reason);
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
if (this.dualPipelined) {
|
|
1418
|
+
this.dualPhase = "rcpt_probe";
|
|
1419
|
+
} else {
|
|
1420
|
+
this.send(`RCPT TO:<${this.probeLocal}@${this.p.domain}>`);
|
|
1421
|
+
this.dualPhase = "rcpt_probe";
|
|
1422
|
+
}
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
if (this.dualPhase === "rcpt_probe") {
|
|
1426
|
+
this.probeOutcome = classifyRcpt(code);
|
|
1427
|
+
if (this.dualPipelined) {
|
|
1428
|
+
this.dualPhase = "rset";
|
|
1429
|
+
} else {
|
|
1430
|
+
this.send("RSET");
|
|
1431
|
+
this.dualPhase = "rset";
|
|
1432
|
+
}
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
if (this.dualPhase === "rset") {
|
|
1436
|
+
if (this.pendingDecision) {
|
|
1437
|
+
this.finish(this.pendingDecision.result, this.pendingDecision.reason);
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1440
|
+
this.decideDualProbe();
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
/** Final decision after both RCPT outcomes are known. Catch-all only when both 250. */
|
|
1445
|
+
decideDualProbe() {
|
|
1446
|
+
if (this.realOutcome === "accept" && this.probeOutcome === "accept") {
|
|
1447
|
+
this.isCatchAllFlag = true;
|
|
1448
|
+
this.finish(true, "valid");
|
|
1449
|
+
} else if (this.realOutcome === "accept") {
|
|
1450
|
+
this.isCatchAllFlag = false;
|
|
1451
|
+
this.finish(true, "valid");
|
|
1452
|
+
} else if (this.realOutcome === "hard_reject") {
|
|
1453
|
+
this.isCatchAllFlag = false;
|
|
1454
|
+
this.finish(false, "not_found");
|
|
1455
|
+
} else if (this.realOutcome === "soft_reject") {
|
|
1456
|
+
this.finish(null, "temporary_failure");
|
|
1457
|
+
} else {
|
|
1458
|
+
this.finish(null, "ambiguous");
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1293
1461
|
finish(result, reason) {
|
|
1294
1462
|
var _a, _b, _c;
|
|
1295
1463
|
if (this.resolved)
|
|
@@ -1313,9 +1481,24 @@ class SMTPProbeConnection {
|
|
|
1313
1481
|
return (_a2 = this.socket) === null || _a2 === void 0 ? void 0 : _a2.destroy();
|
|
1314
1482
|
}, QUIT_DRAIN_MS);
|
|
1315
1483
|
(_c = drain.unref) === null || _c === void 0 ? void 0 : _c.call(drain);
|
|
1316
|
-
this.resolveFn({
|
|
1484
|
+
this.resolveFn({
|
|
1485
|
+
result,
|
|
1486
|
+
reason,
|
|
1487
|
+
...this.lastEnhancedStatus !== void 0 ? { enhancedStatus: this.lastEnhancedStatus } : {},
|
|
1488
|
+
...this.lastResponseCode !== void 0 ? { responseCode: this.lastResponseCode } : {},
|
|
1489
|
+
...this.isCatchAllFlag !== void 0 ? { isCatchAll: this.isCatchAllFlag } : {},
|
|
1490
|
+
transcript: this.transcript,
|
|
1491
|
+
commands: this.commands
|
|
1492
|
+
});
|
|
1317
1493
|
}
|
|
1318
1494
|
}
|
|
1495
|
+
function classifyRcpt(code) {
|
|
1496
|
+
if (code === 250 || code === 251)
|
|
1497
|
+
return "accept";
|
|
1498
|
+
if (code >= 400 && code < 500)
|
|
1499
|
+
return "soft_reject";
|
|
1500
|
+
return "hard_reject";
|
|
1501
|
+
}
|
|
1319
1502
|
|
|
1320
1503
|
class ArrayTranscriptCollector {
|
|
1321
1504
|
constructor() {
|
|
@@ -1988,12 +2171,11 @@ async function verifyEmail(params) {
|
|
|
1988
2171
|
const skipMx = ((_f = params.skipMxForDisposable) !== null && _f !== void 0 ? _f : false) && result.isDisposable;
|
|
1989
2172
|
const skipWhois = ((_g = params.skipDomainWhoisForDisposable) !== null && _g !== void 0 ? _g : false) && result.isDisposable;
|
|
1990
2173
|
await runWhoisChecks(domain, params, result, skipWhois, log, collector);
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
}
|
|
2174
|
+
const wantsMxOrSmtp = ((_h = params.verifyMx) !== null && _h !== void 0 ? _h : true) || ((_j = params.verifySmtp) !== null && _j !== void 0 ? _j : false);
|
|
2175
|
+
if (wantsMxOrSmtp && skipMx) {
|
|
2176
|
+
log(`[verifyEmail] skipping MX/SMTP for disposable: ${params.emailAddress}`);
|
|
2177
|
+
} else if (wantsMxOrSmtp) {
|
|
2178
|
+
await runMxAndSmtp(local, domain, params, result, log, collector);
|
|
1997
2179
|
}
|
|
1998
2180
|
result.metadata.verificationTime = Date.now() - startTime;
|
|
1999
2181
|
if (captureTranscript)
|