@emailcheck/email-validator-js 4.0.1 → 5.0.0-beta.2
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 +128 -30
- package/dist/cli/index.js +60 -20
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.js +155 -24
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +156 -23
- package/dist/index.js.map +1 -1
- package/dist/presets.d.ts +155 -0
- package/dist/types.d.ts +242 -33
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -113,7 +113,8 @@ const result = await verifyEmail({
|
|
|
113
113
|
emailAddress: 'user@mydomain.com',
|
|
114
114
|
verifyMx: true,
|
|
115
115
|
verifySmtp: true,
|
|
116
|
-
|
|
116
|
+
smtpPerAttemptTimeoutMs: 3000, // bounds a single MX × port attempt
|
|
117
|
+
smtpTotalDeadlineMs: 8000, // bounds the entire SMTP probe (NEW in v5)
|
|
117
118
|
});
|
|
118
119
|
|
|
119
120
|
console.log(result.validFormat); // true
|
|
@@ -249,25 +250,31 @@ If you're using an IDE with refactoring support (like VS Code), you can use find
|
|
|
249
250
|
Comprehensive email verification with detailed results and error codes.
|
|
250
251
|
|
|
251
252
|
**Parameters:**
|
|
252
|
-
- `emailAddress` (string, required)
|
|
253
|
-
- `
|
|
254
|
-
- `
|
|
255
|
-
- `
|
|
256
|
-
- `
|
|
257
|
-
- `
|
|
258
|
-
- `
|
|
259
|
-
- `
|
|
260
|
-
- `
|
|
261
|
-
- `
|
|
262
|
-
- `
|
|
263
|
-
- `
|
|
264
|
-
-
|
|
265
|
-
- `
|
|
266
|
-
- `
|
|
267
|
-
- `
|
|
268
|
-
- `
|
|
269
|
-
- `
|
|
270
|
-
- `
|
|
253
|
+
- `emailAddress` (string, required) — Email address to verify.
|
|
254
|
+
- `verifyMx` (boolean) — Resolve MX records (default: `true`).
|
|
255
|
+
- `verifySmtp` (boolean) — Run live SMTP probe (default: `false`).
|
|
256
|
+
- `checkDisposable` (boolean) — Disposable-provider list check (default: `true`).
|
|
257
|
+
- `checkFree` (boolean) — Free-provider list check (default: `true`).
|
|
258
|
+
- `detectName` (boolean) — Extract first/last name from local-part (default: `false`).
|
|
259
|
+
- `suggestDomain` (boolean) — Suggest a corrected domain on typos (default: `true`).
|
|
260
|
+
- `checkDomainAge` (boolean) — WHOIS creation-date lookup (default: `false`).
|
|
261
|
+
- `checkDomainRegistration` (boolean) — WHOIS registration / expiry / lock lookup (default: `false`).
|
|
262
|
+
- `skipMxForDisposable` (boolean) — Skip MX/SMTP for disposable addresses (default: `false`).
|
|
263
|
+
- `skipDomainWhoisForDisposable` (boolean) — Skip WHOIS for disposable addresses (default: `false`).
|
|
264
|
+
- `smtpPort` (number) — Force a specific port for the SMTP probe (overrides the `[25, 587, 465]` walk).
|
|
265
|
+
- **SMTP time-budget controls** (NEW in v5):
|
|
266
|
+
- `smtpPerAttemptTimeoutMs` (number) — Per-MX × port budget in ms (default: `4000`).
|
|
267
|
+
- `smtpTotalDeadlineMs` (number) — Hard cap on total wall-clock for the SMTP probe. Use this from a request handler with a tight latency budget. Default: unbounded.
|
|
268
|
+
- `smtpMaxConsecutiveFailures` (number) — Bail after N connection-class failures in a row (`connection_error` / `connection_timeout` / `connection_closed`). Default: unbounded.
|
|
269
|
+
- `smtpMaxMxHosts` (number) — Cap the MX walk to the first N hostnames. Default: unbounded.
|
|
270
|
+
- `smtpRetry` (`{ attempts, delayMs?, backoff? }`) — Retry connection-class failures on the same MX × port. Default: no retries.
|
|
271
|
+
- `whoisTimeoutMs` (number) — Per-WHOIS-query timeout (default: `5000`).
|
|
272
|
+
- `debug` (boolean) — Per-line `console.debug` trace (default: `false`).
|
|
273
|
+
- `captureTranscript` (boolean) — Populate `result.transcript` with a per-step structured trace (default: `false`).
|
|
274
|
+
- `nameDetectionMethod` (function) — Override the default name-detection heuristic.
|
|
275
|
+
- `domainSuggestionMethod` (function) — Override the default typo-suggestion heuristic.
|
|
276
|
+
- `commonDomains` (string[]) — Custom canonical-domain list for the typo suggester.
|
|
277
|
+
- `cache` (Cache) — Optional shared cache (MX, WHOIS, disposable / free, SMTP, domain results all stored).
|
|
271
278
|
|
|
272
279
|
**Returns:**
|
|
273
280
|
```typescript
|
|
@@ -736,7 +743,7 @@ const result = await verifyEmail({
|
|
|
736
743
|
emailAddress: 'foo@email.com',
|
|
737
744
|
verifyMx: true,
|
|
738
745
|
verifySmtp: true,
|
|
739
|
-
|
|
746
|
+
smtpPerAttemptTimeoutMs: 3000,
|
|
740
747
|
});
|
|
741
748
|
console.log(result.validFormat); // true
|
|
742
749
|
console.log(result.validMx); // true
|
|
@@ -790,16 +797,20 @@ const { smtpResult, port, cached, portCached } = await verifyMailboxSMTP({
|
|
|
790
797
|
domain: 'example.com',
|
|
791
798
|
mxRecords: ['mx.example.com'],
|
|
792
799
|
options: {
|
|
793
|
-
ports: [25, 587, 465],
|
|
794
|
-
|
|
795
|
-
|
|
800
|
+
ports: [25, 587, 465], // Plain → STARTTLS-able → implicit-TLS
|
|
801
|
+
perAttemptTimeoutMs: 5000, // Per-MX × port budget (renamed from `timeout` in v5)
|
|
802
|
+
totalDeadlineMs: 12_000, // Hard cap on total wall-clock (NEW in v5)
|
|
803
|
+
maxConsecutiveFailures: 3, // Bail after N connection-class failures (NEW in v5)
|
|
804
|
+
cache: getDefaultCache(), // Per-isolate verdict + port cache
|
|
796
805
|
debug: false,
|
|
797
|
-
|
|
806
|
+
tlsConfig: { // Renamed from `tls` in v5
|
|
798
807
|
rejectUnauthorized: false,
|
|
799
808
|
minVersion: 'TLSv1.2',
|
|
800
809
|
},
|
|
801
|
-
|
|
802
|
-
|
|
810
|
+
heloHostname: 'your-domain.com', // EHLO/HELO identity (renamed from `hostname` in v5)
|
|
811
|
+
startTls: 'auto', // STARTTLS upgrade on plaintext ports (NEW in v5)
|
|
812
|
+
pipelining: 'auto', // Use SMTP PIPELINING when advertised
|
|
813
|
+
captureTranscript: false, // See "SMTP Transcript Capture" below
|
|
803
814
|
},
|
|
804
815
|
});
|
|
805
816
|
|
|
@@ -807,6 +818,93 @@ console.log(`SMTP result: ${smtpResult.isDeliverable} via port ${port}`);
|
|
|
807
818
|
console.log(`canConnectSmtp=${smtpResult.canConnectSmtp}, error=${smtpResult.error ?? 'none'}`);
|
|
808
819
|
```
|
|
809
820
|
|
|
821
|
+
### Configuration presets (NEW in v5)
|
|
822
|
+
|
|
823
|
+
Don't want to think about timeouts and retries? Pick a preset that matches your deployment shape:
|
|
824
|
+
|
|
825
|
+
```typescript
|
|
826
|
+
import { verifyEmail, VERIFY_EMAIL_PRESETS } from '@emailcheck/email-validator-js';
|
|
827
|
+
|
|
828
|
+
// Lambda / Vercel / Cloudflare-Workers handler
|
|
829
|
+
await verifyEmail({
|
|
830
|
+
emailAddress: 'alice@example.com',
|
|
831
|
+
verifySmtp: true,
|
|
832
|
+
...VERIFY_EMAIL_PRESETS.serverless,
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
// Long-running worker / dyno
|
|
836
|
+
await verifyEmail({ ..., ...VERIFY_EMAIL_PRESETS.dedicated });
|
|
837
|
+
|
|
838
|
+
// Bulk processing
|
|
839
|
+
await verifyEmail({ ..., ...VERIFY_EMAIL_PRESETS.batch });
|
|
840
|
+
|
|
841
|
+
// Form-autocomplete UX (sub-3s)
|
|
842
|
+
await verifyEmail({ ..., ...VERIFY_EMAIL_PRESETS.fast });
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
| Preset | Per-attempt | Total deadline | Max consecutive failures | Max MX | Retry |
|
|
846
|
+
| --- | --- | --- | --- | --- | --- |
|
|
847
|
+
| `serverless` | 2500 ms | **5 s** | 3 | 2 | none — fail fast |
|
|
848
|
+
| `dedicated` | 5000 ms | 30 s | unbounded | unbounded | 1 retry, 500 ms exp backoff |
|
|
849
|
+
| `batch` | 10 000 ms | 60 s | unbounded | unbounded | 2 retries, 1 s exp backoff |
|
|
850
|
+
| `fast` | 1500 ms | **3 s** | 2 | **1** | none — fail fast |
|
|
851
|
+
|
|
852
|
+
`SMTP_PRESETS` is a parallel set with the unprefixed field names for `verifyMailboxSMTP({ options })` callers — same values, same shape.
|
|
853
|
+
|
|
854
|
+
You can spread + override:
|
|
855
|
+
|
|
856
|
+
```typescript
|
|
857
|
+
await verifyEmail({
|
|
858
|
+
emailAddress: 'alice@example.com',
|
|
859
|
+
...VERIFY_EMAIL_PRESETS.serverless,
|
|
860
|
+
smtpTotalDeadlineMs: 3000, // tighter than the preset's 5s
|
|
861
|
+
});
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
### Time-budget controls (NEW in v5)
|
|
865
|
+
|
|
866
|
+
The probe walks `mxRecords × ports` (worst-case 4 × 3 = 12 attempts at 3s each = 36s). Four orthogonal knobs let you bound that:
|
|
867
|
+
|
|
868
|
+
```typescript
|
|
869
|
+
import { verifyEmail, verifyMailboxSMTP } from '@emailcheck/email-validator-js';
|
|
870
|
+
|
|
871
|
+
await verifyEmail({
|
|
872
|
+
emailAddress: 'maria.hernandez+news@yahoo.com',
|
|
873
|
+
verifySmtp: true,
|
|
874
|
+
smtpPerAttemptTimeoutMs: 3000, // Bound a single MX × port attempt
|
|
875
|
+
smtpTotalDeadlineMs: 5000, // Hard cap on total wall-clock
|
|
876
|
+
smtpMaxConsecutiveFailures: 3, // Bail after 3 connection failures in a row
|
|
877
|
+
smtpMaxMxHosts: 2, // Try only the first 2 MXes
|
|
878
|
+
smtpRetry: { // Retry connection-class failures
|
|
879
|
+
attempts: 1,
|
|
880
|
+
delayMs: 200,
|
|
881
|
+
backoff: 'exponential', // or 'fixed'
|
|
882
|
+
},
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
// On verifyMailboxSMTP directly, the same knobs are unprefixed:
|
|
886
|
+
await verifyMailboxSMTP({
|
|
887
|
+
local: 'alice', domain: 'example.com', mxRecords: [...],
|
|
888
|
+
options: {
|
|
889
|
+
perAttemptTimeoutMs: 3000,
|
|
890
|
+
totalDeadlineMs: 5000,
|
|
891
|
+
maxConsecutiveFailures: 3,
|
|
892
|
+
maxMxHosts: 2,
|
|
893
|
+
retry: { attempts: 1, delayMs: 200, backoff: 'exponential' },
|
|
894
|
+
},
|
|
895
|
+
});
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
| Knob | Default | When to use |
|
|
899
|
+
| --- | --- | --- |
|
|
900
|
+
| `(smtp)PerAttemptTimeoutMs` | `4000` (verifyEmail) / `3000` (verifyMailboxSMTP) | Per-MX × port budget. Bound a single attempt. |
|
|
901
|
+
| `(smtp)TotalDeadlineMs` | unbounded | Hard wall-clock cap. Use from a request handler with a tight latency budget. |
|
|
902
|
+
| `(smtp)MaxConsecutiveFailures` | unbounded | Cut off probes when the network path is dead. Counter resets on any non-connection-class outcome. |
|
|
903
|
+
| `(smtp)MaxMxHosts` | unbounded | Cap the MX walk regardless of how many DNS returned. |
|
|
904
|
+
| `(smtp)Retry` | no retries | Retry connection-class failures on the same MX × port. Definitive answers (250 / 550 / 552) are never retried. |
|
|
905
|
+
|
|
906
|
+
`PerAttemptTimeout` and `TotalDeadline` are **orthogonal** — use both when you have both a per-attempt SLO and a hard caller-side budget.
|
|
907
|
+
|
|
810
908
|
### Custom SMTP step sequence
|
|
811
909
|
|
|
812
910
|
Override the default `greeting → EHLO → MAIL FROM → RCPT TO` walk for advanced cases:
|
|
@@ -839,11 +937,11 @@ const { smtpResult } = await verifyMailboxSMTP({
|
|
|
839
937
|
local: 'user',
|
|
840
938
|
domain: 'example.com',
|
|
841
939
|
mxRecords: ['mx.example.com'],
|
|
842
|
-
options: { ports: [25, 587],
|
|
940
|
+
options: { ports: [25, 587], perAttemptTimeoutMs: 5000, captureTranscript: true },
|
|
843
941
|
});
|
|
844
942
|
|
|
845
|
-
// Both arrays aggregate across every port attempted, prefixed
|
|
846
|
-
// "25|s| 220 mx.example.com ESMTP"
|
|
943
|
+
// Both arrays aggregate across every MX × port attempted, prefixed:
|
|
944
|
+
// "mx.example.com:25|s| 220 mx.example.com ESMTP"
|
|
847
945
|
// "25|c| EHLO localhost"
|
|
848
946
|
console.log(smtpResult.transcript);
|
|
849
947
|
console.log(smtpResult.commands);
|
package/dist/cli/index.js
CHANGED
|
@@ -1052,15 +1052,21 @@ async function verifyMailboxSMTP(params) {
|
|
|
1052
1052
|
const { local, domain, options = {} } = params;
|
|
1053
1053
|
const mxRecords = params.mxRecords ?? [];
|
|
1054
1054
|
const ports = (options.ports ?? DEFAULT_PORTS).filter((port) => Number.isInteger(port) && port > 0 && port < 65536);
|
|
1055
|
-
const
|
|
1056
|
-
const tlsConfig = options.
|
|
1057
|
-
const
|
|
1055
|
+
const perAttemptTimeoutMs = options.perAttemptTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
1056
|
+
const tlsConfig = options.tlsConfig ?? true;
|
|
1057
|
+
const heloHostname = options.heloHostname ?? "localhost";
|
|
1058
1058
|
const debug = options.debug ?? false;
|
|
1059
1059
|
const captureTranscript = options.captureTranscript ?? false;
|
|
1060
1060
|
const sequence = options.sequence;
|
|
1061
1061
|
const cache = options.cache;
|
|
1062
1062
|
const log = debug ? (...args) => console.log("[SMTP]", ...args) : () => {
|
|
1063
1063
|
};
|
|
1064
|
+
const totalDeadlineMs = options.totalDeadlineMs;
|
|
1065
|
+
const maxConsecutiveFailures = options.maxConsecutiveFailures;
|
|
1066
|
+
const maxMxHosts = options.maxMxHosts;
|
|
1067
|
+
const retryAttempts = options.retry?.attempts ?? 0;
|
|
1068
|
+
const retryDelayMs = options.retry?.delayMs ?? 200;
|
|
1069
|
+
const retryBackoff = options.retry?.backoff ?? "exponential";
|
|
1064
1070
|
const startedAtMs = Date.now();
|
|
1065
1071
|
const primaryMx = mxRecords[0];
|
|
1066
1072
|
if (!primaryMx) {
|
|
@@ -1074,9 +1080,9 @@ async function verifyMailboxSMTP(params) {
|
|
|
1074
1080
|
const probeOptions = {
|
|
1075
1081
|
local,
|
|
1076
1082
|
domain,
|
|
1077
|
-
|
|
1083
|
+
perAttemptTimeoutMs,
|
|
1078
1084
|
tlsConfig,
|
|
1079
|
-
|
|
1085
|
+
heloHostname,
|
|
1080
1086
|
sequence,
|
|
1081
1087
|
log,
|
|
1082
1088
|
catchAllProbeLocal: options.catchAllProbeLocal,
|
|
@@ -1097,17 +1103,41 @@ async function verifyMailboxSMTP(params) {
|
|
|
1097
1103
|
const mxHostsTried = [];
|
|
1098
1104
|
let mxAttempts = 0;
|
|
1099
1105
|
let portAttempts = 0;
|
|
1106
|
+
let consecutiveFailures = 0;
|
|
1100
1107
|
let lastReason = "all_attempts_failed";
|
|
1101
1108
|
let lastEnhancedStatus;
|
|
1102
1109
|
let lastResponseCode;
|
|
1103
|
-
|
|
1110
|
+
let stoppedEarly = null;
|
|
1111
|
+
const isConnectionFailure = (reason) => reason === "connection_error" || reason === "connection_timeout" || reason === "connection_closed" || reason === "socket_timeout";
|
|
1112
|
+
const retryDelayFor = (attemptIndex) => retryBackoff === "exponential" ? retryDelayMs * 2 ** (attemptIndex - 1) : retryDelayMs;
|
|
1113
|
+
const probeWithRetry = async (mxHost, port) => {
|
|
1114
|
+
let lastProbe = await runProbe({ ...probeOptions, mxHost, port });
|
|
1115
|
+
for (let i = 1; i <= retryAttempts; i++) {
|
|
1116
|
+
if (lastProbe.result !== null || !isConnectionFailure(lastProbe.reason)) break;
|
|
1117
|
+
const delay = retryDelayFor(i);
|
|
1118
|
+
log(`retry ${i}/${retryAttempts} on ${mxHost}:${port} after ${delay}ms (last: ${lastProbe.reason})`);
|
|
1119
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
1120
|
+
portAttempts++;
|
|
1121
|
+
lastProbe = await runProbe({ ...probeOptions, mxHost, port });
|
|
1122
|
+
}
|
|
1123
|
+
return lastProbe;
|
|
1124
|
+
};
|
|
1125
|
+
outer: for (const mxHost of mxRecords) {
|
|
1126
|
+
if (maxMxHosts !== void 0 && mxAttempts >= maxMxHosts) {
|
|
1127
|
+
stoppedEarly = "max_mx_hosts";
|
|
1128
|
+
break;
|
|
1129
|
+
}
|
|
1104
1130
|
mxHostsTried.push(mxHost);
|
|
1105
1131
|
mxAttempts++;
|
|
1106
1132
|
const portsForThisMx = mxHost === primaryMx && cachedPort ? [cachedPort, ...ports.filter((p) => p !== cachedPort)] : ports;
|
|
1107
1133
|
for (const port of portsForThisMx) {
|
|
1134
|
+
if (totalDeadlineMs !== void 0 && Date.now() - startedAtMs >= totalDeadlineMs) {
|
|
1135
|
+
stoppedEarly = "deadline";
|
|
1136
|
+
break outer;
|
|
1137
|
+
}
|
|
1108
1138
|
portAttempts++;
|
|
1109
1139
|
log(`Testing ${mxHost}:${port}`);
|
|
1110
|
-
const probe = await
|
|
1140
|
+
const probe = await probeWithRetry(mxHost, port);
|
|
1111
1141
|
collectTranscript(transcript, commands, probe, mxHost, port);
|
|
1112
1142
|
lastReason = probe.reason;
|
|
1113
1143
|
if (probe.enhancedStatus !== void 0) lastEnhancedStatus = probe.enhancedStatus;
|
|
@@ -1123,8 +1153,14 @@ async function verifyMailboxSMTP(params) {
|
|
|
1123
1153
|
if (mxHost === primaryMx) await safeCacheSet(portCache, primaryMx, port);
|
|
1124
1154
|
return { smtpResult: smtpResult2, cached: false, port, portCached: cachedPort === port };
|
|
1125
1155
|
}
|
|
1156
|
+
consecutiveFailures = isConnectionFailure(probe.reason) ? consecutiveFailures + 1 : 0;
|
|
1157
|
+
if (maxConsecutiveFailures !== void 0 && consecutiveFailures >= maxConsecutiveFailures) {
|
|
1158
|
+
stoppedEarly = "consecutive_failures";
|
|
1159
|
+
break outer;
|
|
1160
|
+
}
|
|
1126
1161
|
}
|
|
1127
1162
|
}
|
|
1163
|
+
if (stoppedEarly) log(`Stopped early: ${stoppedEarly}`);
|
|
1128
1164
|
log(`All MX\xD7port attempts failed (mx=${mxAttempts}, port=${portAttempts})`);
|
|
1129
1165
|
const metrics = makeMetrics(mxHostsTried, mxAttempts, portAttempts, void 0, startedAtMs);
|
|
1130
1166
|
const smtpResult = {
|
|
@@ -1275,16 +1311,16 @@ class SMTPProbeConnection {
|
|
|
1275
1311
|
} else {
|
|
1276
1312
|
this.socket = net__namespace.connect({ host: this.p.mxHost, port: this.p.port }, onConnect);
|
|
1277
1313
|
}
|
|
1278
|
-
this.socket.setTimeout(this.p.
|
|
1314
|
+
this.socket.setTimeout(this.p.perAttemptTimeoutMs, () => this.finish(null, "socket_timeout"));
|
|
1279
1315
|
this.socket.on("error", () => this.finish(null, "connection_error"));
|
|
1280
1316
|
this.socket.on("close", () => this.finish(null, "connection_closed"));
|
|
1281
1317
|
}
|
|
1282
1318
|
armConnectionTimer() {
|
|
1283
|
-
this.connectionTimer = setTimeout(() => this.finish(null, "connection_timeout"), this.p.
|
|
1319
|
+
this.connectionTimer = setTimeout(() => this.finish(null, "connection_timeout"), this.p.perAttemptTimeoutMs);
|
|
1284
1320
|
}
|
|
1285
1321
|
resetStepTimer() {
|
|
1286
1322
|
if (this.stepTimer) clearTimeout(this.stepTimer);
|
|
1287
|
-
this.stepTimer = setTimeout(() => this.finish(null, "step_timeout"), this.p.
|
|
1323
|
+
this.stepTimer = setTimeout(() => this.finish(null, "step_timeout"), this.p.perAttemptTimeoutMs);
|
|
1288
1324
|
}
|
|
1289
1325
|
send(cmd) {
|
|
1290
1326
|
if (this.resolved) return;
|
|
@@ -1309,10 +1345,10 @@ class SMTPProbeConnection {
|
|
|
1309
1345
|
return;
|
|
1310
1346
|
// server-driven; nothing to send
|
|
1311
1347
|
case SMTPStep.ehlo:
|
|
1312
|
-
this.send(`EHLO ${this.p.
|
|
1348
|
+
this.send(`EHLO ${this.p.heloHostname}`);
|
|
1313
1349
|
return;
|
|
1314
1350
|
case SMTPStep.helo:
|
|
1315
|
-
this.send(`HELO ${this.p.
|
|
1351
|
+
this.send(`HELO ${this.p.heloHostname}`);
|
|
1316
1352
|
return;
|
|
1317
1353
|
case SMTPStep.startTls:
|
|
1318
1354
|
this.executeStartTls();
|
|
@@ -1384,7 +1420,7 @@ class SMTPProbeConnection {
|
|
|
1384
1420
|
this.supportsStartTls = false;
|
|
1385
1421
|
this.supportsPipelining = false;
|
|
1386
1422
|
this.postUpgradeReEhlo = true;
|
|
1387
|
-
this.send(`EHLO ${this.p.
|
|
1423
|
+
this.send(`EHLO ${this.p.heloHostname}`);
|
|
1388
1424
|
});
|
|
1389
1425
|
this.socket = tlsSocket;
|
|
1390
1426
|
this.socket.on("data", this.onData);
|
|
@@ -1392,7 +1428,7 @@ class SMTPProbeConnection {
|
|
|
1392
1428
|
this.finish(null, this.tlsUpgrading ? "tls_handshake_failed" : "connection_error");
|
|
1393
1429
|
});
|
|
1394
1430
|
this.socket.on("close", () => this.finish(null, "connection_closed"));
|
|
1395
|
-
this.socket.setTimeout(this.p.
|
|
1431
|
+
this.socket.setTimeout(this.p.perAttemptTimeoutMs, () => this.finish(null, "socket_timeout"));
|
|
1396
1432
|
}
|
|
1397
1433
|
/**
|
|
1398
1434
|
* Send the dual-probe envelope (real RCPT + probe RCPT + RSET). Pipelined
|
|
@@ -2328,13 +2364,13 @@ async function runWhoisChecks(domain, params, result, skipWhois, log, collector)
|
|
|
2328
2364
|
log(`[verifyEmail] WHOIS checks skipped for disposable: ${domain}`);
|
|
2329
2365
|
return;
|
|
2330
2366
|
}
|
|
2331
|
-
const
|
|
2367
|
+
const whoisTimeoutMs = params.whoisTimeoutMs ?? 5e3;
|
|
2332
2368
|
const debug = params.debug ?? false;
|
|
2333
2369
|
if (params.checkDomainAge) {
|
|
2334
2370
|
try {
|
|
2335
2371
|
result.domainAge = await collector.record(
|
|
2336
2372
|
"whois-age",
|
|
2337
|
-
() => getDomainAge(domain,
|
|
2373
|
+
() => getDomainAge(domain, whoisTimeoutMs, debug, params.cache),
|
|
2338
2374
|
(info) => ({
|
|
2339
2375
|
domain,
|
|
2340
2376
|
found: info !== null,
|
|
@@ -2354,7 +2390,7 @@ async function runWhoisChecks(domain, params, result, skipWhois, log, collector)
|
|
|
2354
2390
|
try {
|
|
2355
2391
|
result.domainRegistration = await collector.record(
|
|
2356
2392
|
"whois-registration",
|
|
2357
|
-
() => getDomainRegistrationStatus(domain,
|
|
2393
|
+
() => getDomainRegistrationStatus(domain, whoisTimeoutMs, debug, params.cache),
|
|
2358
2394
|
(info) => ({
|
|
2359
2395
|
domain,
|
|
2360
2396
|
found: info !== null,
|
|
@@ -2427,7 +2463,11 @@ async function runSmtp(local, domain, mxRecords, params, result, log, collector)
|
|
|
2427
2463
|
options: {
|
|
2428
2464
|
cache: params.cache,
|
|
2429
2465
|
ports: resolveSmtpPorts(params.smtpPort, mxRecords[0]),
|
|
2430
|
-
|
|
2466
|
+
perAttemptTimeoutMs: params.smtpPerAttemptTimeoutMs ?? 4e3,
|
|
2467
|
+
totalDeadlineMs: params.smtpTotalDeadlineMs,
|
|
2468
|
+
maxConsecutiveFailures: params.smtpMaxConsecutiveFailures,
|
|
2469
|
+
maxMxHosts: params.smtpMaxMxHosts,
|
|
2470
|
+
retry: params.smtpRetry,
|
|
2431
2471
|
debug: params.debug ?? false,
|
|
2432
2472
|
// Forward transcript capture so the SMTP step's details include
|
|
2433
2473
|
// the full per-port transcript when the caller asked for it.
|
|
@@ -2613,7 +2653,7 @@ async function run(args, deps = {}) {
|
|
|
2613
2653
|
emailAddress: args.email,
|
|
2614
2654
|
verifyMx: args.verifyMx,
|
|
2615
2655
|
verifySmtp: args.verifySmtp,
|
|
2616
|
-
|
|
2656
|
+
smtpPerAttemptTimeoutMs: args.timeoutMs ?? 5e3,
|
|
2617
2657
|
debug: args.debug,
|
|
2618
2658
|
smtpPort: args.smtpPort,
|
|
2619
2659
|
checkDisposable: args.checkDisposable,
|
|
@@ -2622,7 +2662,7 @@ async function run(args, deps = {}) {
|
|
|
2622
2662
|
suggestDomain: args.suggestDomain,
|
|
2623
2663
|
checkDomainAge: args.checkDomainAge,
|
|
2624
2664
|
checkDomainRegistration: args.checkDomainRegistration,
|
|
2625
|
-
|
|
2665
|
+
whoisTimeoutMs: args.whoisTimeoutMs ?? 5e3,
|
|
2626
2666
|
captureTranscript: args.captureTranscript
|
|
2627
2667
|
});
|
|
2628
2668
|
if (args.quiet) {
|