Skip to content

Instantly share code, notes, and snippets.

@MikeRatcliffe
Last active March 18, 2021 13:07
Show Gist options
  • Save MikeRatcliffe/1fd09c303c996be5ea0f4d3536da8c6b to your computer and use it in GitHub Desktop.
Save MikeRatcliffe/1fd09c303c996be5ea0f4d3536da8c6b to your computer and use it in GitHub Desktop.
#!/usr/bin/env node
/**
* Very simple node script to validate an email address against its SMTP server.
*
* Usage:
* node validate.js email@mail.com
*
* This script uses the following process to validate an email address:
* 1. Get the domain of the email
* 2. Grab the DNS MX records for that domain
* 3. Create a TCP connection to the smtp server
* 4. Send a EHLO message
* 5. Send a MAIL FROM message
* 6. Send a RCPT TO message
* 7. Return an object indicating whether the email passed SMTP validation:
* {
* valid: true, // true: email is valid, false: email is
* // invalid.
* err: "error message", // If an error is encountered then this will
* // contain the error string. If there is no
* // error then this value is null.
* }
*
* @author Mike Ratcliffe
*/
const dns = require("dns");
const net = require("net");
if (process.argv.length < 3) {
console.log(
"Very simple node script to validate an email address against its SMTP server.\n\nUsage:\n node validate.js email@mail.com"
);
return;
}
const dnsPromises = dns.promises;
const email = process.argv.pop();
(async () => {
const start = new Date();
await validate(email, 3000, (valid, err) => {
console.log(`valid`, valid);
console.log(`err`, err);
console.log(`Run time: ${new Date() - start}ms`);
});
})();
/**
*
* @param {String} email
* email to validate.
* @param {Number} timeout
* timeout in ms.
* @param {Function} callback
* Callback function e.g. myFunc(valid, err) {}
*/
async function validate(email, timeout, callback) {
const domain = email.split("@")[1];
const [{ exchange }] = await dnsPromises.resolveMx(domain);
let receivedData = false;
let timedOut = false;
const socket = net.createConnection(25, exchange);
socket.setEncoding("ascii");
socket.setTimeout(timeout);
socket.on("error", (error) => {
console.log("error", error);
socket.emit("fail", error);
});
socket.on("close", (hadError) => {
if (!receivedData && !hadError && !timedOut) {
callback(
false,
"Mail server closed connection without sending any data."
);
}
});
socket.on("fail", (msg) => {
if (socket.writable && !socket.destroyed) {
socket.write(`quit\r\n`);
socket.end();
socket.destroy();
}
callback(false, msg);
});
socket.on("success", () => {
if (socket.writable && !socket.destroyed) {
socket.write(`quit\r\n`);
socket.end();
socket.destroy();
}
callback(true, null);
});
const commands = [
`helo ${exchange}\r\n`,
`mail from: <${email}>\r\n`,
`rcpt to: <${email}>\r\n`,
];
let i = 0;
socket.on("next", () => {
if (i < 3) {
if (socket.writable) {
socket.write(commands[i++]);
} else {
socket.emit("fail", "SMTP communication unexpectedly closed.");
}
} else {
socket.emit("success");
}
});
socket.on("timeout", () => {
timedOut = true;
socket.emit("fail", "Timeout");
});
socket.on("connect", () => {
socket.on("data", (msg) => {
receivedData = true;
if (hasCode(msg, 220) || hasCode(msg, 250)) {
socket.emit("next", msg);
} else if (hasCode(msg, 550)) {
socket.emit("fail", "Mailbox not found.");
} else {
// Possible error codes
const ErrorCodes = {
211: "SMTP Error: A system status or help reply.",
214: "SMTP Error: Help Message.",
220: "SMTP Error: The server is ready.",
221: "SMTP Error: The server is ending the conversation.",
250: "SMTP Error: The requested action was completed.",
251: "SMTP Error: The specified user is not local, but the server will forward the mail message.",
354: 'SMTP Error: This is a reply to the DATA command. After getting this, start sending the body of the mail message, ending with "\r\n.\r\n."',
421: "SMTP Error: The mail server will be shut down. Save the mail message and try again later.",
450: "SMTP Error: The mailbox that you are trying to reach is busy. Wait a little while and try again.",
451: "SMTP Error: The requested action was not done. Some error occurmiles in the mail server.",
452: "SMTP Error: The requested action was not done. The mail server ran out of system storage.",
500: "SMTP Error: The last command contained a syntax error or the command line was too long.",
501: "SMTP Error: The parameters or arguments in the last command contained a syntax error.",
502: "SMTP Error: The mail server has not implemented the last command.",
503: "SMTP Error: The last command was sent out of sequence. For example, you might have sent DATA before sending RECV.",
504: "SMTP Error: One of the parameters of the last command has not been implemented by the server.",
550: "SMTP Error: The mailbox that you are trying to reach can't be found or you don't have access rights.",
551: "SMTP Error: The specified user is not local; part of the text of the message will contain a forwarding address.",
552: "SMTP Error: The mailbox that you are trying to reach has run out of space. Store the message and try again tomorrow or in a few days-after the user gets a chance to delete some messages.",
553: "SMTP Error: The mail address that you specified was not syntactically correct.",
554: "SMTP Error: The mail transaction has failed for an unknown reason.",
};
const [code] = Object.keys(ErrorCodes).filter((x) => hasCode(msg, x));
socket.emit("fail", ErrorCodes[code] || "Unrecognized SMTP response.");
}
});
});
function hasCode(message, code) {
return (
message.indexOf(`${code}`) === 0 || message.indexOf(`${code}\n`) > -1
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment