@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.js
CHANGED
|
@@ -4,6 +4,7 @@ var tinyLru = require('tiny-lru');
|
|
|
4
4
|
var psl = require('psl');
|
|
5
5
|
var stringSimilarityJs = require('string-similarity-js');
|
|
6
6
|
var node_dns = require('node:dns');
|
|
7
|
+
var node_crypto = require('node:crypto');
|
|
7
8
|
var net$1 = require('node:net');
|
|
8
9
|
var tls = require('node:tls');
|
|
9
10
|
|
|
@@ -680,9 +681,9 @@ function parseCompositeNamePart(part) {
|
|
|
680
681
|
cleaned = pureAlpha;
|
|
681
682
|
confidence = 0.6;
|
|
682
683
|
} else {
|
|
683
|
-
const
|
|
684
|
-
if (
|
|
685
|
-
cleaned =
|
|
684
|
+
const baseMatch = part.match(/^([a-zA-Z]+)\d*$/);
|
|
685
|
+
if (baseMatch) {
|
|
686
|
+
cleaned = baseMatch[1];
|
|
686
687
|
confidence = 0.75;
|
|
687
688
|
} else {
|
|
688
689
|
cleaned = part;
|
|
@@ -696,9 +697,7 @@ function parseCompositeNamePart(part) {
|
|
|
696
697
|
confidence = Math.min(1, confidence + 0.2);
|
|
697
698
|
}
|
|
698
699
|
}
|
|
699
|
-
|
|
700
|
-
const base = baseMatch ? baseMatch[1] : part;
|
|
701
|
-
return { base, hasNumbers, cleaned, confidence };
|
|
700
|
+
return { hasNumbers, cleaned, confidence };
|
|
702
701
|
}
|
|
703
702
|
function isLikelyName(str, allowNumbers = false, allowSingleLetter = false) {
|
|
704
703
|
if (!str)
|
|
@@ -989,6 +988,9 @@ function parseDsn(reply) {
|
|
|
989
988
|
return null;
|
|
990
989
|
return { class: Number(match[1]), subject: Number(match[2]), detail: Number(match[3]) };
|
|
991
990
|
}
|
|
991
|
+
function dsnToString(dsn) {
|
|
992
|
+
return `${dsn.class}.${dsn.subject}.${dsn.detail}`;
|
|
993
|
+
}
|
|
992
994
|
function isPolicyBlock(reply) {
|
|
993
995
|
const dsn = parseDsn(reply);
|
|
994
996
|
return (dsn === null || dsn === void 0 ? void 0 : dsn.class) === 5 && (dsn === null || dsn === void 0 ? void 0 : dsn.subject) === 7;
|
|
@@ -1009,8 +1011,11 @@ function isInvalidMailboxError(reply) {
|
|
|
1009
1011
|
return false;
|
|
1010
1012
|
return true;
|
|
1011
1013
|
}
|
|
1014
|
+
function defaultProbeLocal() {
|
|
1015
|
+
return `${node_crypto.randomBytes(8).toString("hex")}-noexist`;
|
|
1016
|
+
}
|
|
1012
1017
|
async function verifyMailboxSMTP(params) {
|
|
1013
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
1018
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1014
1019
|
const { local, domain, options = {} } = params;
|
|
1015
1020
|
const mxRecords = (_a = params.mxRecords) !== null && _a !== void 0 ? _a : [];
|
|
1016
1021
|
const ports = ((_b = options.ports) !== null && _b !== void 0 ? _b : DEFAULT_PORTS).filter((port) => Number.isInteger(port) && port > 0 && port < 65536);
|
|
@@ -1023,16 +1028,29 @@ async function verifyMailboxSMTP(params) {
|
|
|
1023
1028
|
const cache = options.cache;
|
|
1024
1029
|
const log = debug ? (...args) => console.log("[SMTP]", ...args) : () => {
|
|
1025
1030
|
};
|
|
1026
|
-
const
|
|
1027
|
-
|
|
1031
|
+
const startedAtMs = Date.now();
|
|
1032
|
+
const primaryMx = mxRecords[0];
|
|
1033
|
+
if (!primaryMx) {
|
|
1028
1034
|
log("No MX records found");
|
|
1029
|
-
|
|
1035
|
+
const metrics2 = makeMetrics([], 0, 0, void 0, startedAtMs);
|
|
1036
|
+
return { smtpResult: failureResult("no_mx_records", metrics2), cached: false, port: 0, portCached: false };
|
|
1030
1037
|
}
|
|
1031
|
-
log(`Verifying ${local}@${domain} via ${
|
|
1038
|
+
log(`Verifying ${local}@${domain} via ${primaryMx} (mx count=${mxRecords.length})`);
|
|
1032
1039
|
const transcript = [];
|
|
1033
1040
|
const commands = [];
|
|
1041
|
+
const probeOptions = {
|
|
1042
|
+
local,
|
|
1043
|
+
domain,
|
|
1044
|
+
timeout,
|
|
1045
|
+
tlsConfig,
|
|
1046
|
+
hostname,
|
|
1047
|
+
sequence,
|
|
1048
|
+
log,
|
|
1049
|
+
catchAllProbeLocal: options.catchAllProbeLocal,
|
|
1050
|
+
pipelining: (_h = options.pipelining) !== null && _h !== void 0 ? _h : "auto"
|
|
1051
|
+
};
|
|
1034
1052
|
const verdictCache = cache ? getCacheStore(cache, "smtp") : null;
|
|
1035
|
-
const verdictKey = `${
|
|
1053
|
+
const verdictKey = `${primaryMx}:${local}@${domain}`;
|
|
1036
1054
|
if (verdictCache) {
|
|
1037
1055
|
const cachedResult = await safeCacheGet(verdictCache, verdictKey);
|
|
1038
1056
|
if (cachedResult) {
|
|
@@ -1041,81 +1059,99 @@ async function verifyMailboxSMTP(params) {
|
|
|
1041
1059
|
}
|
|
1042
1060
|
}
|
|
1043
1061
|
const portCache = cache ? getCacheStore(cache, "smtpPort") : null;
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
});
|
|
1059
|
-
collectTranscript(transcript, commands, probe,
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1062
|
+
const cachedPort = portCache ? await safeCacheGet(portCache, primaryMx) : null;
|
|
1063
|
+
const mxHostsTried = [];
|
|
1064
|
+
let mxAttempts = 0;
|
|
1065
|
+
let portAttempts = 0;
|
|
1066
|
+
let lastReason = "all_attempts_failed";
|
|
1067
|
+
let lastEnhancedStatus;
|
|
1068
|
+
let lastResponseCode;
|
|
1069
|
+
for (const mxHost of mxRecords) {
|
|
1070
|
+
mxHostsTried.push(mxHost);
|
|
1071
|
+
mxAttempts++;
|
|
1072
|
+
const portsForThisMx = mxHost === primaryMx && cachedPort ? [cachedPort, ...ports.filter((p) => p !== cachedPort)] : ports;
|
|
1073
|
+
for (const port of portsForThisMx) {
|
|
1074
|
+
portAttempts++;
|
|
1075
|
+
log(`Testing ${mxHost}:${port}`);
|
|
1076
|
+
const probe = await runProbe({ ...probeOptions, mxHost, port });
|
|
1077
|
+
collectTranscript(transcript, commands, probe, mxHost, port);
|
|
1078
|
+
lastReason = probe.reason;
|
|
1079
|
+
if (probe.enhancedStatus !== void 0)
|
|
1080
|
+
lastEnhancedStatus = probe.enhancedStatus;
|
|
1081
|
+
if (probe.responseCode !== void 0)
|
|
1082
|
+
lastResponseCode = probe.responseCode;
|
|
1083
|
+
if (probe.result !== null) {
|
|
1084
|
+
const metrics2 = makeMetrics(mxHostsTried, mxAttempts, portAttempts, mxHost, startedAtMs);
|
|
1085
|
+
const smtpResult2 = toSmtpVerificationResult(probe, {
|
|
1086
|
+
transcript: captureTranscript ? transcript : void 0,
|
|
1087
|
+
commands: captureTranscript ? commands : void 0,
|
|
1088
|
+
metrics: metrics2
|
|
1089
|
+
});
|
|
1090
|
+
await safeCacheSet(verdictCache, verdictKey, smtpResult2);
|
|
1091
|
+
if (mxHost === primaryMx)
|
|
1092
|
+
await safeCacheSet(portCache, primaryMx, port);
|
|
1093
|
+
return { smtpResult: smtpResult2, cached: false, port, portCached: cachedPort === port };
|
|
1094
|
+
}
|
|
1074
1095
|
}
|
|
1075
1096
|
}
|
|
1076
|
-
log(
|
|
1097
|
+
log(`All MX\xD7port attempts failed (mx=${mxAttempts}, port=${portAttempts})`);
|
|
1098
|
+
const metrics = makeMetrics(mxHostsTried, mxAttempts, portAttempts, void 0, startedAtMs);
|
|
1099
|
+
const smtpResult = {
|
|
1100
|
+
...failureResult(lastReason, metrics),
|
|
1101
|
+
...lastEnhancedStatus !== void 0 ? { enhancedStatus: lastEnhancedStatus } : {},
|
|
1102
|
+
...lastResponseCode !== void 0 ? { responseCode: lastResponseCode } : {},
|
|
1103
|
+
...captureTranscript ? { transcript: [...transcript], commands: [...commands] } : {}
|
|
1104
|
+
};
|
|
1105
|
+
return { smtpResult, cached: false, port: 0, portCached: false };
|
|
1106
|
+
}
|
|
1107
|
+
function makeMetrics(mxHostsTried, mxAttempts, portAttempts, mxHostUsed, startedAtMs) {
|
|
1077
1108
|
return {
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
},
|
|
1082
|
-
|
|
1083
|
-
port: 0,
|
|
1084
|
-
portCached: false
|
|
1109
|
+
mxAttempts,
|
|
1110
|
+
portAttempts,
|
|
1111
|
+
mxHostsTried: [...mxHostsTried],
|
|
1112
|
+
...mxHostUsed !== void 0 ? { mxHostUsed } : {},
|
|
1113
|
+
totalDurationMs: Date.now() - startedAtMs
|
|
1085
1114
|
};
|
|
1086
1115
|
}
|
|
1087
|
-
function collectTranscript(transcript, commands, probe, port) {
|
|
1116
|
+
function collectTranscript(transcript, commands, probe, mxHost, port) {
|
|
1117
|
+
const prefix = `${mxHost}:${port}`;
|
|
1088
1118
|
for (const line of probe.transcript)
|
|
1089
|
-
transcript.push(`${
|
|
1119
|
+
transcript.push(`${prefix}|s| ${line}`);
|
|
1090
1120
|
for (const cmd of probe.commands)
|
|
1091
|
-
commands.push(`${
|
|
1121
|
+
commands.push(`${prefix}|c| ${cmd}`);
|
|
1092
1122
|
}
|
|
1093
|
-
function failureResult(
|
|
1123
|
+
function failureResult(reason, metrics) {
|
|
1094
1124
|
return {
|
|
1095
1125
|
canConnectSmtp: false,
|
|
1096
1126
|
hasFullInbox: false,
|
|
1097
1127
|
isCatchAll: false,
|
|
1098
1128
|
isDeliverable: false,
|
|
1099
1129
|
isDisabled: false,
|
|
1100
|
-
error,
|
|
1101
|
-
|
|
1102
|
-
|
|
1130
|
+
error: reason,
|
|
1131
|
+
checkedAt: Date.now(),
|
|
1132
|
+
metrics
|
|
1103
1133
|
};
|
|
1104
1134
|
}
|
|
1105
|
-
function toSmtpVerificationResult(
|
|
1106
|
-
|
|
1135
|
+
function toSmtpVerificationResult(probe, extras) {
|
|
1136
|
+
var _a;
|
|
1137
|
+
const result = probe.result;
|
|
1138
|
+
const out = {
|
|
1107
1139
|
canConnectSmtp: result !== null,
|
|
1108
|
-
hasFullInbox:
|
|
1109
|
-
isCatchAll: false,
|
|
1140
|
+
hasFullInbox: probe.reason === "over_quota",
|
|
1141
|
+
isCatchAll: (_a = probe.isCatchAll) !== null && _a !== void 0 ? _a : false,
|
|
1110
1142
|
isDeliverable: result === true,
|
|
1111
1143
|
isDisabled: result === false,
|
|
1112
|
-
error: result === true ? void 0 :
|
|
1113
|
-
|
|
1114
|
-
|
|
1144
|
+
error: result === true ? void 0 : probe.reason,
|
|
1145
|
+
checkedAt: Date.now(),
|
|
1146
|
+
metrics: extras.metrics,
|
|
1147
|
+
...probe.enhancedStatus !== void 0 ? { enhancedStatus: probe.enhancedStatus } : {},
|
|
1148
|
+
...probe.responseCode !== void 0 ? { responseCode: probe.responseCode } : {}
|
|
1115
1149
|
};
|
|
1116
|
-
if (
|
|
1117
|
-
|
|
1118
|
-
|
|
1150
|
+
if (extras.transcript)
|
|
1151
|
+
out.transcript = [...extras.transcript];
|
|
1152
|
+
if (extras.commands)
|
|
1153
|
+
out.commands = [...extras.commands];
|
|
1154
|
+
return out;
|
|
1119
1155
|
}
|
|
1120
1156
|
async function safeCacheGet(store, key) {
|
|
1121
1157
|
if (!store)
|
|
@@ -1147,6 +1183,12 @@ class SMTPProbeConnection {
|
|
|
1147
1183
|
this.currentStepIndex = 0;
|
|
1148
1184
|
this.transcript = [];
|
|
1149
1185
|
this.commands = [];
|
|
1186
|
+
this.supportsPipelining = false;
|
|
1187
|
+
this.dualPhase = "idle";
|
|
1188
|
+
this.realOutcome = "pending";
|
|
1189
|
+
this.probeOutcome = "pending";
|
|
1190
|
+
this.dualPipelined = false;
|
|
1191
|
+
this.pendingDecision = null;
|
|
1150
1192
|
this.onData = (data) => {
|
|
1151
1193
|
if (this.resolved)
|
|
1152
1194
|
return;
|
|
@@ -1170,6 +1212,7 @@ class SMTPProbeConnection {
|
|
|
1170
1212
|
minVersion: "TLSv1.2",
|
|
1171
1213
|
...typeof p.tlsConfig === "object" ? p.tlsConfig : {}
|
|
1172
1214
|
};
|
|
1215
|
+
this.probeLocal = p.catchAllProbeLocal ? p.catchAllProbeLocal(p.local, p.domain) : defaultProbeLocal();
|
|
1173
1216
|
}
|
|
1174
1217
|
run() {
|
|
1175
1218
|
return new Promise((resolve) => {
|
|
@@ -1230,6 +1273,7 @@ class SMTPProbeConnection {
|
|
|
1230
1273
|
switch (step) {
|
|
1231
1274
|
case exports.SMTPStep.greeting:
|
|
1232
1275
|
return;
|
|
1276
|
+
// server-driven; nothing to send
|
|
1233
1277
|
case exports.SMTPStep.ehlo:
|
|
1234
1278
|
this.send(`EHLO ${this.p.hostname}`);
|
|
1235
1279
|
return;
|
|
@@ -1242,39 +1286,87 @@ class SMTPProbeConnection {
|
|
|
1242
1286
|
return;
|
|
1243
1287
|
}
|
|
1244
1288
|
case exports.SMTPStep.rcptTo:
|
|
1245
|
-
this.
|
|
1289
|
+
this.executeEnvelope();
|
|
1246
1290
|
return;
|
|
1247
1291
|
}
|
|
1248
1292
|
}
|
|
1293
|
+
/**
|
|
1294
|
+
* Send the dual-probe envelope (real RCPT + probe RCPT + RSET). Pipelined
|
|
1295
|
+
* when the MX advertised PIPELINING (or `pipelining: 'force'`); sequential
|
|
1296
|
+
* otherwise.
|
|
1297
|
+
*/
|
|
1298
|
+
executeEnvelope() {
|
|
1299
|
+
var _a;
|
|
1300
|
+
const wantsPipelining = this.p.pipelining === "force" || this.p.pipelining === "auto" && this.supportsPipelining;
|
|
1301
|
+
const realCmd = `RCPT TO:<${this.p.local}@${this.p.domain}>`;
|
|
1302
|
+
if (wantsPipelining) {
|
|
1303
|
+
this.dualPipelined = true;
|
|
1304
|
+
const probeCmd = `RCPT TO:<${this.probeLocal}@${this.p.domain}>`;
|
|
1305
|
+
const rsetCmd = "RSET";
|
|
1306
|
+
this.commands.push(realCmd, probeCmd, rsetCmd);
|
|
1307
|
+
this.p.log(`\u2192 ${realCmd}`);
|
|
1308
|
+
this.p.log(`\u2192 ${probeCmd}`);
|
|
1309
|
+
this.p.log(`\u2192 ${rsetCmd}`);
|
|
1310
|
+
(_a = this.socket) === null || _a === void 0 ? void 0 : _a.write(`${realCmd}\r
|
|
1311
|
+
${probeCmd}\r
|
|
1312
|
+
${rsetCmd}\r
|
|
1313
|
+
`);
|
|
1314
|
+
this.dualPhase = "rcpt_real";
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
this.dualPipelined = false;
|
|
1318
|
+
this.send(realCmd);
|
|
1319
|
+
this.dualPhase = "rcpt_real";
|
|
1320
|
+
}
|
|
1249
1321
|
processLine(line) {
|
|
1250
1322
|
if (this.resolved)
|
|
1251
1323
|
return;
|
|
1252
1324
|
this.transcript.push(line);
|
|
1253
1325
|
this.p.log(`\u2190 ${line}`);
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1326
|
+
const codeStr = line.slice(0, 3);
|
|
1327
|
+
const numericCode = /^\d{3}$/.test(codeStr) ? parseInt(codeStr, 10) : null;
|
|
1328
|
+
if (numericCode !== null)
|
|
1329
|
+
this.lastResponseCode = numericCode;
|
|
1330
|
+
const dsn = parseDsn(line);
|
|
1331
|
+
if (dsn)
|
|
1332
|
+
this.lastEnhancedStatus = dsnToString(dsn);
|
|
1333
|
+
if (this.dualPhase === "idle" || this.dualPhase === "rcpt_real") {
|
|
1334
|
+
if (isHighVolume(line)) {
|
|
1335
|
+
this.finish(true, "high_volume");
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
if (isOverQuota(line)) {
|
|
1339
|
+
this.isCatchAllFlag = false;
|
|
1340
|
+
this.finish(false, "over_quota");
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
if (isInvalidMailboxError(line)) {
|
|
1344
|
+
this.isCatchAllFlag = false;
|
|
1345
|
+
this.finish(false, "not_found");
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1261
1348
|
}
|
|
1262
|
-
if (
|
|
1263
|
-
this.
|
|
1349
|
+
if (MULTILINE_RE.test(line)) {
|
|
1350
|
+
const step = this.steps[this.currentStepIndex];
|
|
1351
|
+
if ((step === exports.SMTPStep.ehlo || step === exports.SMTPStep.helo) && line.startsWith("250-")) {
|
|
1352
|
+
const upper = line.toUpperCase();
|
|
1353
|
+
if (upper.includes("PIPELINING"))
|
|
1354
|
+
this.supportsPipelining = true;
|
|
1355
|
+
}
|
|
1264
1356
|
return;
|
|
1265
1357
|
}
|
|
1266
|
-
if (MULTILINE_RE.test(line))
|
|
1267
|
-
return;
|
|
1268
|
-
const code = line.slice(0, 3);
|
|
1269
|
-
const numericCode = /^\d{3}$/.test(code) ? parseInt(code, 10) : null;
|
|
1270
1358
|
if (numericCode === null) {
|
|
1271
1359
|
this.finish(null, "unrecognized_response");
|
|
1272
1360
|
return;
|
|
1273
1361
|
}
|
|
1274
|
-
this.dispatch(numericCode);
|
|
1362
|
+
this.dispatch(numericCode, line);
|
|
1275
1363
|
}
|
|
1276
|
-
dispatch(code) {
|
|
1364
|
+
dispatch(code, line) {
|
|
1277
1365
|
const step = this.steps[this.currentStepIndex];
|
|
1366
|
+
if (this.dualPhase !== "idle" && step === exports.SMTPStep.rcptTo) {
|
|
1367
|
+
this.handleEnvelopeReply(code, line);
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1278
1370
|
switch (step) {
|
|
1279
1371
|
case exports.SMTPStep.greeting:
|
|
1280
1372
|
if (code === 220)
|
|
@@ -1301,17 +1393,93 @@ class SMTPProbeConnection {
|
|
|
1301
1393
|
this.finish(null, "mail_from_rejected");
|
|
1302
1394
|
return;
|
|
1303
1395
|
case exports.SMTPStep.rcptTo:
|
|
1304
|
-
|
|
1305
|
-
this.finish(true, "valid");
|
|
1306
|
-
else if (code === 552 || code === 452)
|
|
1307
|
-
this.finish(false, "over_quota");
|
|
1308
|
-
else if (code >= 400 && code < 500)
|
|
1309
|
-
this.finish(null, "temporary_failure");
|
|
1310
|
-
else
|
|
1311
|
-
this.finish(null, "ambiguous");
|
|
1396
|
+
this.handleEnvelopeReply(code, line);
|
|
1312
1397
|
return;
|
|
1313
1398
|
}
|
|
1314
1399
|
}
|
|
1400
|
+
/**
|
|
1401
|
+
* Dual-probe / pipelined-envelope reply router. Demuxes server replies for
|
|
1402
|
+
* the three queued commands (real RCPT, probe RCPT, RSET) and resolves
|
|
1403
|
+
* with the catch-all-aware verdict.
|
|
1404
|
+
*/
|
|
1405
|
+
handleEnvelopeReply(code, line) {
|
|
1406
|
+
if (this.dualPhase === "rcpt_real") {
|
|
1407
|
+
this.realOutcome = classifyRcpt(code);
|
|
1408
|
+
if (code === 552 || code === 452 || isOverQuota(line)) {
|
|
1409
|
+
this.isCatchAllFlag = false;
|
|
1410
|
+
if (this.dualPipelined) {
|
|
1411
|
+
this.pendingDecision = { result: false, reason: "over_quota" };
|
|
1412
|
+
this.dualPhase = "rcpt_probe";
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
this.finish(false, "over_quota");
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
if (this.realOutcome === "soft_reject") {
|
|
1419
|
+
if (this.dualPipelined) {
|
|
1420
|
+
this.pendingDecision = { result: null, reason: "temporary_failure" };
|
|
1421
|
+
this.dualPhase = "rcpt_probe";
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
this.finish(null, "temporary_failure");
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
if (this.realOutcome === "hard_reject") {
|
|
1428
|
+
const reason = isInvalidMailboxError(line) ? "not_found" : "ambiguous";
|
|
1429
|
+
const result = reason === "not_found" ? false : null;
|
|
1430
|
+
this.isCatchAllFlag = false;
|
|
1431
|
+
if (this.dualPipelined) {
|
|
1432
|
+
this.pendingDecision = { result, reason };
|
|
1433
|
+
this.dualPhase = "rcpt_probe";
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
this.finish(result, reason);
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
if (this.dualPipelined) {
|
|
1440
|
+
this.dualPhase = "rcpt_probe";
|
|
1441
|
+
} else {
|
|
1442
|
+
this.send(`RCPT TO:<${this.probeLocal}@${this.p.domain}>`);
|
|
1443
|
+
this.dualPhase = "rcpt_probe";
|
|
1444
|
+
}
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
if (this.dualPhase === "rcpt_probe") {
|
|
1448
|
+
this.probeOutcome = classifyRcpt(code);
|
|
1449
|
+
if (this.dualPipelined) {
|
|
1450
|
+
this.dualPhase = "rset";
|
|
1451
|
+
} else {
|
|
1452
|
+
this.send("RSET");
|
|
1453
|
+
this.dualPhase = "rset";
|
|
1454
|
+
}
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
if (this.dualPhase === "rset") {
|
|
1458
|
+
if (this.pendingDecision) {
|
|
1459
|
+
this.finish(this.pendingDecision.result, this.pendingDecision.reason);
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
this.decideDualProbe();
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
/** Final decision after both RCPT outcomes are known. Catch-all only when both 250. */
|
|
1467
|
+
decideDualProbe() {
|
|
1468
|
+
if (this.realOutcome === "accept" && this.probeOutcome === "accept") {
|
|
1469
|
+
this.isCatchAllFlag = true;
|
|
1470
|
+
this.finish(true, "valid");
|
|
1471
|
+
} else if (this.realOutcome === "accept") {
|
|
1472
|
+
this.isCatchAllFlag = false;
|
|
1473
|
+
this.finish(true, "valid");
|
|
1474
|
+
} else if (this.realOutcome === "hard_reject") {
|
|
1475
|
+
this.isCatchAllFlag = false;
|
|
1476
|
+
this.finish(false, "not_found");
|
|
1477
|
+
} else if (this.realOutcome === "soft_reject") {
|
|
1478
|
+
this.finish(null, "temporary_failure");
|
|
1479
|
+
} else {
|
|
1480
|
+
this.finish(null, "ambiguous");
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1315
1483
|
finish(result, reason) {
|
|
1316
1484
|
var _a, _b, _c;
|
|
1317
1485
|
if (this.resolved)
|
|
@@ -1335,9 +1503,24 @@ class SMTPProbeConnection {
|
|
|
1335
1503
|
return (_a2 = this.socket) === null || _a2 === void 0 ? void 0 : _a2.destroy();
|
|
1336
1504
|
}, QUIT_DRAIN_MS);
|
|
1337
1505
|
(_c = drain.unref) === null || _c === void 0 ? void 0 : _c.call(drain);
|
|
1338
|
-
this.resolveFn({
|
|
1506
|
+
this.resolveFn({
|
|
1507
|
+
result,
|
|
1508
|
+
reason,
|
|
1509
|
+
...this.lastEnhancedStatus !== void 0 ? { enhancedStatus: this.lastEnhancedStatus } : {},
|
|
1510
|
+
...this.lastResponseCode !== void 0 ? { responseCode: this.lastResponseCode } : {},
|
|
1511
|
+
...this.isCatchAllFlag !== void 0 ? { isCatchAll: this.isCatchAllFlag } : {},
|
|
1512
|
+
transcript: this.transcript,
|
|
1513
|
+
commands: this.commands
|
|
1514
|
+
});
|
|
1339
1515
|
}
|
|
1340
1516
|
}
|
|
1517
|
+
function classifyRcpt(code) {
|
|
1518
|
+
if (code === 250 || code === 251)
|
|
1519
|
+
return "accept";
|
|
1520
|
+
if (code >= 400 && code < 500)
|
|
1521
|
+
return "soft_reject";
|
|
1522
|
+
return "hard_reject";
|
|
1523
|
+
}
|
|
1341
1524
|
|
|
1342
1525
|
class ArrayTranscriptCollector {
|
|
1343
1526
|
constructor() {
|
|
@@ -2010,12 +2193,11 @@ async function verifyEmail(params) {
|
|
|
2010
2193
|
const skipMx = ((_f = params.skipMxForDisposable) !== null && _f !== void 0 ? _f : false) && result.isDisposable;
|
|
2011
2194
|
const skipWhois = ((_g = params.skipDomainWhoisForDisposable) !== null && _g !== void 0 ? _g : false) && result.isDisposable;
|
|
2012
2195
|
await runWhoisChecks(domain, params, result, skipWhois, log, collector);
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
}
|
|
2196
|
+
const wantsMxOrSmtp = ((_h = params.verifyMx) !== null && _h !== void 0 ? _h : true) || ((_j = params.verifySmtp) !== null && _j !== void 0 ? _j : false);
|
|
2197
|
+
if (wantsMxOrSmtp && skipMx) {
|
|
2198
|
+
log(`[verifyEmail] skipping MX/SMTP for disposable: ${params.emailAddress}`);
|
|
2199
|
+
} else if (wantsMxOrSmtp) {
|
|
2200
|
+
await runMxAndSmtp(local, domain, params, result, log, collector);
|
|
2019
2201
|
}
|
|
2020
2202
|
result.metadata.verificationTime = Date.now() - startTime;
|
|
2021
2203
|
if (captureTranscript)
|