-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.mjs
92 lines (83 loc) · 2.93 KB
/
index.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// ES Module syntax
import { promises as dns } from 'dns';
import net from 'net';
import pLimit from 'p-limit';
const MAX_EMAIL_LEN = 300;
const CONNECTION_TIMEOUT = 5000;
const MAX_PARALLEL_CONNECTIONS = 10;
const limit = pLimit(MAX_PARALLEL_CONNECTIONS);
const mxCache = new Map();
async function resolveMxCached(domain) {
if (mxCache.has(domain)) return mxCache.get(domain);
try {
const addresses = (await dns.resolveMx(domain)).sort((a, b) => a.priority - b.priority);
mxCache.set(domain, addresses);
return addresses;
} catch (err) {
mxCache.set(domain, null);
return null;
}
}
export async function checkEmailExistence(email, timeout = CONNECTION_TIMEOUT, fromEmail = email) {
if (email.length > MAX_EMAIL_LEN || !/^\S+@\S+$/.test(email)) {
return { email, valid: false, undetermined: false };
}
const domain = email.split('@')[1];
const addresses = await resolveMxCached(domain);
if (!addresses) {
return { email, valid: false, undetermined: false };
}
const checkPromises = addresses.map(({ exchange }) =>
limit(() => checkMXServer(exchange, email, fromEmail, timeout))
);
try {
const result = await Promise.race(checkPromises);
return { email, ...result };
} catch (err) {
return { email, valid: false, undetermined: true };
}
}
async function checkMXServer(mxHost, email, fromEmail, timeout) {
const commands = [`helo ${mxHost}`, `mail from: <${fromEmail}>`, `rcpt to: <${email}>`];
return new Promise((resolve, reject) => {
const port = 25;
const conn = net.createConnection({ host: mxHost, port });
conn.setEncoding('ascii');
conn.setTimeout(timeout);
let i = 0;
let response = false;
conn.on('data', (data) => {
if (/^220|^250/.test(data)) {
if (i < commands.length) {
conn.write(commands[i] + '\r\n');
i++;
} else {
response = true;
conn.end();
}
} else if (/^550/.test(data)) {
response = false;
conn.end();
} else {
conn.end();
}
});
conn.on('error', () => {
conn.destroy();
reject();
});
conn.on('timeout', () => {
conn.destroy();
reject();
});
conn.on('end', () => {
resolve({ valid: response, undetermined: !response });
});
});
}
export async function checkMultipleEmails(emails, timeout = CONNECTION_TIMEOUT, fromEmail) {
const results = await Promise.allSettled(
emails.map((email) => limit(() => checkEmailExistence(email, timeout, fromEmail)))
);
return results.map((result) => (result.status === 'fulfilled' ? result.value : { email: result.reason, valid: false, undetermined: true }));
}