├── .babelrc ├── package.json ├── .gitignore ├── README.MD └── src └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "targets": { 7 | "node": "v6.11.2" 8 | } 9 | } 10 | ] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mail-confirm", 3 | "version": "1.0.2", 4 | "description": "A Node Js API for three stage email validation including, pattern, MX, and mailbox existence validation.", 5 | "main": "./build/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "./node_modules/.bin/babel src --out-dir build", 9 | "doc": "jsdoc2md ./build/index.js" 10 | }, 11 | "keywords": [ 12 | "mailgun", 13 | "email", 14 | "validation", 15 | "mailbox", 16 | "verification", 17 | "smtp", 18 | "email verification", 19 | "email validation", 20 | "mailbox validation", 21 | "VRFY", 22 | "EHLO", 23 | "HELO", 24 | "MAIL FROM", 25 | "RCPT TO" 26 | ], 27 | "author": { 28 | "name": "Elias Hussary", 29 | "email": "eliashussary@gmail.com" 30 | }, 31 | "repository": { 32 | "type": "git", 33 | "url": "https://github.com/eliashussary/mail-confirm" 34 | }, 35 | "license": "MIT", 36 | "devDependencies": { 37 | "babel-cli": "^6.24.1", 38 | "babel-preset-env": "^1.6.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | # build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # MailConfirm 2 | A Node Js API for three stage email validation including the following; 3 | * email pattern validation, 4 | * MX mail server existence, 5 | * mailbox existence. 6 | 7 | *NOTE: True mailbox existence may only be performed, with certainty, by sending a verification email and having the user verify their email.* 8 | 9 | ## Install 10 | ```sh 11 | # yarn 12 | yarn add mail-confirm 13 | # npm 14 | npm i mail-confirm 15 | ``` 16 | 17 | ## Usage 18 | #### Usage Notes 19 | * Ensure you execute this in an environment where SMTP port 25 is accessible. Failing to do so will throw error *Mailbox check failed*. 20 | * Internal instance methods are exposed as Static methods. If you wish to build your own email verification routine, or only require one of these staged methods, simply call MailConfirm.[methodName]. *See API Documentation below.* 21 | ```Javascript 22 | import MailConfirm from 'mail-confirm' 23 | 24 | // promises 25 | const email = new MailConfirm({ 26 | emailAddress: 'test@gmail.com', 27 | timeout: 2000, 28 | mailFrom: 'my@email.com', 29 | invalidMailboxKeywords: ['noreply', 'noemail'] 30 | }) 31 | 32 | email.check().then(console.log).catch(console.log) 33 | 34 | // async/await 35 | const check = async (emailAddress) => { 36 | try{ 37 | const email = new MailConfirm({ emailAddress }) 38 | const result = await email.check() 39 | return result 40 | }catch(err){ 41 | throw new Error(err) 42 | } 43 | } 44 | 45 | check('test@gmail.com') 46 | /* 47 | output => 48 | { emailAddress: 'test@gmail.com', 49 | timeout: 2000, 50 | invalidMailboxKeywords: [], 51 | mailFrom: 'my@email.com', 52 | mailbox: 'test', 53 | hostname: 'gmail.com', 54 | mxRecords: 55 | [ { exchange: 'gmail-smtp-in.l.google.com', priority: 5 }, 56 | { exchange: 'alt1.gmail-smtp-in.l.google.com', priority: 10 }, 57 | { exchange: 'alt2.gmail-smtp-in.l.google.com', priority: 20 }, 58 | { exchange: 'alt3.gmail-smtp-in.l.google.com', priority: 30 }, 59 | { exchange: 'alt4.gmail-smtp-in.l.google.com', priority: 40 } ], 60 | smtpMessages: 61 | [ { command: 'HELO gmail-smtp-in.l.google.com', 62 | message: '220 mx.google.com ESMTP n6si1346674qtk.310 - gsmtp\r\n', 63 | status: 220 }, 64 | { command: 'MAIL FROM: ', 65 | message: '250 mx.google.com at your service\r\n', 66 | status: 250 }, 67 | { command: 'RCPT TO: ', 68 | message: '250 2.1.0 OK n6si1346674qtk.310 - gsmtp\r\n', 69 | status: 250 } ], 70 | isValidPattern: true, 71 | isValidMx: true, 72 | isValidMailbox: true, 73 | result: 'Mailbox is valid.' } 74 | */ 75 | ``` 76 | 77 | 78 | 79 | ## API Documentation 80 | 81 | 82 | 83 | ## MailConfirm 84 | **Kind**: global class 85 | 86 | * [MailConfirm](#MailConfirm) 87 | * [new MailConfirm(config)](#new_MailConfirm_new) 88 | * _instance_ 89 | * [.check()](#MailConfirm+check) ⇒ Object 90 | * _static_ 91 | * [.resolvePattern(emailAddress, [invalidMailboxKeywords])](#MailConfirm.resolvePattern) ⇒ boolean 92 | * [.resolveMx(hostname)](#MailConfirm.resolveMx) ⇒ Array.<Object> 93 | * [.resolveSmtpMailbox(config)](#MailConfirm.resolveSmtpMailbox) ⇒ Array.<object> 94 | 95 | 96 | 97 | ### new MailConfirm(config) 98 | Email address validation and SMTP verification API. 99 | 100 | 101 | | Param | Type | Description | 102 | | --- | --- | --- | 103 | | config | Object | The email address you want to validate. | 104 | | config.emailAddress | string | The email address you want to validate. | 105 | | [config.mailFrom] | string | The email address used for the mail from during SMTP mailbox validation. | 106 | | [config.invalidMailboxKeywords] | Array.<string> | Keywords you want to void, i.e. noemail, noreply etc. | 107 | | [config.timeout] | number | The timeout parameter for SMTP mailbox validation. | 108 | 109 | 110 | 111 | ### mailConfirm.check() ⇒ Object 112 | Runs the email validation routine and supplies a final result. 113 | 114 | **Kind**: instance method of [MailConfirm](#MailConfirm) 115 | **Returns**: Object - - The instance state object containing all of the isValid* boolean checks, MX Records, and SMTP Messages. 116 | 117 | 118 | 119 | ### MailConfirm.resolvePattern(emailAddress, [invalidMailboxKeywords]) ⇒ boolean 120 | Determines if the email address pattern is valid based on regex and invalid keyword check. 121 | 122 | **Kind**: static method of [MailConfirm](#MailConfirm) 123 | 124 | | Param | Type | Default | Description | 125 | | --- | --- | --- | --- | 126 | | emailAddress | string | | The full email address ypu want to check. | 127 | | [invalidMailboxKeywords] | Array.<string> | [] | An array of keywords to invalidate your check, ie. noreply 128 | , noemail, etc. | 129 | 130 | 131 | 132 | ### MailConfirm.resolveMx(hostname) ⇒ Array.<Object> 133 | Wrap of dns.resolveMx native method. 134 | 135 | **Kind**: static method of [MailConfirm](#MailConfirm) 136 | **Returns**: Array.<Object> - - Returns MX records array { priority, exchange } 137 | 138 | | Param | Type | Description | 139 | | --- | --- | --- | 140 | | hostname | string | The hostname you want to resolve, i.e. gmail.com | 141 | 142 | 143 | 144 | ### MailConfirm.resolveSmtpMailbox(config) ⇒ Array.<object> 145 | Runs the SMTP mailbox check. Commands for HELO/EHLO, MAIL FROM, RCPT TO. 146 | 147 | **Kind**: static method of [MailConfirm](#MailConfirm) 148 | **Returns**: Array.<object> - - Object of SMTP responses [ {command, status, message} ] 149 | 150 | | Param | Type | Description | 151 | | --- | --- | --- | 152 | | config | Object | Object of parameters for Smtp Mailbox resolution. | 153 | | config.emailAddress | string | The email address you want to check. | 154 | | config.mxRecords | Array.<object> | The MX Records array supplied from resolveMx. | 155 | | config.timeout | number | Timeout parameter for the SMTP routine. | 156 | | config.mailFrom | string | The email address supplied to the MAIL FROM SMTP command. | 157 | 158 | 159 | 160 | **Title**: MailConfirm 161 | **Author**: Elias Hussary 162 | **License**: MIT 163 | **Copyright**: (C) 2017 Elias Hussary -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @title MailConfirm 3 | * @author Elias Hussary 4 | * @license MIT 5 | * @copyright (C) 2017 Elias Hussary 6 | */ 7 | 8 | import dns from 'dns' 9 | import net from 'net' 10 | const resolveMx = hostname => { 11 | return new Promise((resolve, reject) => { 12 | dns.resolveMx(hostname, (err, val) => { 13 | if (err) { 14 | return reject(err) 15 | } 16 | resolve(val) 17 | }) 18 | }) 19 | } 20 | 21 | /** 22 | * Email address validation and SMTP verification API. 23 | 24 | * @param {Object} config - The email address you want to validate. 25 | * @param {string} config.emailAddress - The email address you want to validate. 26 | * @param {string} [config.mailFrom] - The email address used for the mail from during SMTP mailbox validation. 27 | * @param {string[]} [config.invalidMailboxKeywords] - Keywords you want to void, i.e. noemail, noreply etc. 28 | * @param {number} [config.timeout] - The timeout parameter for SMTP mailbox validation. 29 | * @returns {instance} 30 | * @class MailConfirm 31 | */ 32 | class MailConfirm { 33 | constructor({ emailAddress, invalidMailboxKeywords, timeout, mailFrom }) { 34 | this.state = { 35 | // args 36 | emailAddress, 37 | timeout: timeout || 2000, 38 | invalidMailboxKeywords: invalidMailboxKeywords || [], 39 | mailFrom: mailFrom || 'email@example.org', 40 | // helpers 41 | mailbox: emailAddress.split('@')[0], 42 | hostname: emailAddress.split('@')[1], 43 | mxRecords: [], 44 | smtpMessages: [], 45 | // results 46 | isValidPattern: false, 47 | isValidMx: false, 48 | isValidMailbox: false, 49 | result: '' 50 | } 51 | } 52 | 53 | /** 54 | * Determines if the email address pattern is valid based on regex and invalid keyword check. 55 | * 56 | * @static 57 | * @param {string} emailAddress - The full email address ypu want to check. 58 | * @param {string[]} [invalidMailboxKeywords=[]] - An array of keywords to invalidate your check, ie. noreply, noemail, etc. 59 | * @returns {boolean} 60 | * @memberof MailConfirm 61 | */ 62 | static resolvePattern(emailAddress, invalidMailboxKeywords = []) { 63 | const mailbox = emailAddress.split('@')[0] 64 | const regex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ 65 | const isValidPattern = 66 | regex.test(emailAddress) || invalidMailboxKeywords.indexOf(mailbox) === -1 67 | return isValidPattern 68 | } 69 | 70 | // private instance method 71 | _resolvePattern(emailAddress, invalidMailboxKeywords = []) { 72 | return MailConfirm.resolvePattern(emailAddress, invalidMailboxKeywords) 73 | } 74 | 75 | /** 76 | * Wrap of dns.resolveMx native method. 77 | * 78 | * @static 79 | * @param {string} hostname - The hostname you want to resolve, i.e. gmail.com 80 | * @returns {Object[]} - Returns MX records array { priority, exchange } 81 | * @memberof MailConfirm 82 | */ 83 | static async resolveMx(hostname) { 84 | // mx check 85 | try { 86 | let mxRecords = await resolveMx(hostname) 87 | return mxRecords.sort((a, b) => a.priority - b.priority) 88 | } catch (err) { 89 | return [] 90 | } 91 | } 92 | 93 | // private instance method 94 | _resolveMx(hostname) { 95 | return MailConfirm.resolveMx(hostname) 96 | } 97 | 98 | /** 99 | * Runs the SMTP mailbox check. Commands for HELO/EHLO, MAIL FROM, RCPT TO. 100 | * 101 | * @static 102 | * @param {Object} config - Object of parameters for Smtp Mailbox resolution. 103 | * @param {string} config.emailAddress - The email address you want to check. 104 | * @param {object[]} config.mxRecords - The MX Records array supplied from resolveMx. 105 | * @param {number} config.timeout - Timeout parameter for the SMTP routine. 106 | * @param {string} config.mailFrom - The email address supplied to the MAIL FROM SMTP command. 107 | * @returns {object[]} - Object of SMTP responses [ {command, status, message} ] 108 | * @memberof MailConfirm 109 | */ 110 | static resolveSmtpMailbox({ emailAddress, mxRecords, timeout, mailFrom }) { 111 | return new Promise((resolve, reject) => { 112 | const host = mxRecords[0].exchange 113 | const commands = [ 114 | `HELO ${host}`, 115 | `MAIL FROM: <${mailFrom}>`, 116 | `RCPT TO: <${emailAddress}>` 117 | ] 118 | 119 | const stepMax = commands.length - 1 120 | let step = 0 121 | const smtp = net.createConnection({ port: 25, host }) 122 | let smtpMessages = [] 123 | 124 | smtp.setEncoding('ascii') 125 | smtp.setTimeout(timeout) 126 | 127 | smtp.on('next', () => { 128 | if (step < stepMax) { 129 | smtp.write(commands[step] + '\r\n') 130 | step++ 131 | } else { 132 | smtp.end(() => { 133 | resolve(smtpMessages) 134 | }) 135 | } 136 | }) 137 | 138 | smtp.on('error', err => { 139 | smtp.end(() => { 140 | reject(err) 141 | }) 142 | }) 143 | 144 | smtp.on('data', data => { 145 | const status = parseInt(data.substring(0, 3)) 146 | smtpMessages.push({ 147 | command: commands[step], 148 | message: data, 149 | status 150 | }) 151 | if (status > 200) { 152 | smtp.emit('next') 153 | } 154 | }) 155 | }) 156 | } 157 | // private instance method 158 | _resolveSmtpMailbox({ emailAddress, mxRecords, timeout, mailFrom }) { 159 | return MailConfirm.resolveSmtpMailbox({ 160 | emailAddress, 161 | mxRecords, 162 | timeout, 163 | mailFrom 164 | }) 165 | } 166 | 167 | /** 168 | * Runs the email validation routine and supplies a final result. 169 | * 170 | * @returns {Object} - The instance state object containing all of the isValid* boolean checks, MX Records, and SMTP Messages. 171 | * @memberof MailConfirm 172 | */ 173 | async check() { 174 | // pattern check 175 | const isValidPattern = this._resolvePattern( 176 | this.state.emailAddress, 177 | this.state.invalidMailboxKeywords 178 | ) 179 | this.state.isValidPattern = isValidPattern 180 | 181 | if (!isValidPattern) { 182 | this.state.result = 'Email pattern is invalid.' 183 | return this.state 184 | } 185 | 186 | // mx check 187 | try { 188 | const mxRecords = await this._resolveMx(this.state.hostname) 189 | const isValidMx = mxRecords.length > 0 190 | this.state.mxRecords = mxRecords 191 | this.state.isValidMx = isValidMx 192 | 193 | if (!isValidMx) { 194 | this.state.result = 'Email server is invalid or not available.' 195 | return this.state 196 | } 197 | } catch (err) { 198 | throw new Error('MX record check failed.') 199 | } 200 | 201 | // mailbox check 202 | try { 203 | const { emailAddress, mxRecords, timeout, mailFrom } = this.state 204 | const smtpMessages = await this._resolveSmtpMailbox({ 205 | emailAddress, 206 | mxRecords, 207 | timeout, 208 | mailFrom 209 | }) 210 | this.state.smtpMessages = smtpMessages 211 | const isComplete = smtpMessages.length === 3 212 | let result = '' 213 | 214 | if (isComplete) { 215 | const { status } = smtpMessages[2] 216 | // OK RESPONSE 217 | if (status === 250) { 218 | result = 'Mailbox is valid.' 219 | this.state.result = result 220 | this.state.isValidMailbox = true 221 | } else { 222 | result = 'Mailbox is invalid.' 223 | this.state.result = result 224 | this.state.isValidMailbox = false 225 | } 226 | } else { 227 | result = 'Could not validate mailbox.' 228 | this.state.result = result 229 | this.state.isValidMailbox = false 230 | } 231 | return this.state 232 | } catch (err) { 233 | throw new Error('Mailbox check failed.') 234 | } 235 | } 236 | } 237 | 238 | module.exports = MailConfirm 239 | --------------------------------------------------------------------------------