-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.cjs
120 lines (105 loc) · 4.15 KB
/
index.cjs
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// CommonJS syntax
const dns = require('dns/promises');
const net = require('net');
// Constants
const MAX_EMAIL_LEN = 300;
const CONNECTION_TIMEOUT = 5000;
const MAX_PARALLEL_CONNECTIONS = 10;
const mxCache = new Map();
async function getPLimit() {
const { default: pLimit } = await import('p-limit'); // Dynamically import p-limit
return pLimit(MAX_PARALLEL_CONNECTIONS);
}
// Function to resolve MX records with caching
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;
}
}
// Function to check email existence
async function checkEmailExistence(email, timeout = CONNECTION_TIMEOUT, fromEmail = email) {
const limit = await getPLimit(); // Get the limit instance
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 };
}
}
// Function to check MX server
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) => {
console.log(`Received: ${data}`); // Log responses for debugging
if (/^220|^250/.test(data)) { // Server ready or command accepted
if (i < commands.length) {
conn.write(commands[i] + '\r\n');
i++;
} else {
response = true; // Successfully processed all commands
conn.end();
}
} else if (/^550/.test(data)) { // Mailbox unavailable
if (/blocked using Spamhaus/.test(data)) {
reject(new Error('Client host is blocked (Spamhaus)'));
} else {
response = false; // Invalid email address
conn.end();
}
} else if (/^421|^450|^451/.test(data)) { // Temporary failures
response = false; // Treat as undetermined
conn.end();
} else {
console.log(`Unexpected response: ${data}`);
conn.end(); // Close the connection for unexpected responses
}
});
conn.on('error', (err) => {
conn.destroy();
reject(new Error(`Connection error with ${mxHost}: ${err.message}`));
});
conn.on('timeout', () => {
conn.destroy();
reject(new Error(`Connection timeout with ${mxHost}`));
});
conn.on('end', () => {
resolve({ valid: response, undetermined: !response });
});
});
}
// Function to check multiple emails
async function checkMultipleEmails(emails, timeout = CONNECTION_TIMEOUT, fromEmail) {
const limit = await getPLimit(); // Get the limit instance
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.email, valid: false, undetermined: true }
);
}
// Export functions
module.exports = { checkEmailExistence, checkMultipleEmails };