├── .gitignore ├── LICENSE ├── jest.config.ts ├── package-lock.json ├── package.json ├── readme.md ├── src ├── emails │ ├── emails.spec.ts │ └── emails.ts ├── index.ts ├── interfaces │ ├── emails.interface.ts │ └── ping-email.interface.ts ├── log │ ├── log.spec.ts │ └── log.ts ├── ping-email.spec.ts ├── ping-email.ts └── types │ └── disposable-email.d.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # Build files 5 | dist 6 | 7 | # Environments 8 | .env 9 | .env.* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Pingback 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import { JestConfigWithTsJest } from "ts-jest/"; 2 | 3 | const config: JestConfigWithTsJest = { 4 | verbose: true, 5 | clearMocks: true, 6 | preset: "ts-jest", 7 | restoreMocks: true, 8 | prettierPath: null, 9 | testEnvironment: "node", 10 | }; 11 | 12 | export default config; 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ping-email", 3 | "version": "1.0.8", 4 | "description": "Node.js SMTP Email Verification Library", 5 | "main": "./dist/index.js", 6 | "module": "./dist/index.mjs", 7 | "types": "./dist/index.d.ts", 8 | "files": [ 9 | "dist/**" 10 | ], 11 | "exports": { 12 | ".": { 13 | "import": { 14 | "types": "./dist/index.d.ts", 15 | "default": "./dist/index.mjs" 16 | }, 17 | "require": { 18 | "types": "./dist/index.d.ts", 19 | "default": "./dist/index.js" 20 | } 21 | } 22 | }, 23 | "scripts": { 24 | "test": "jest", 25 | "test:watch": "jest --watch", 26 | "prepublishOnly": "npm run build", 27 | "build": "tsup src/index.ts --format esm,cjs --dts" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/getpingback/ping-email.git" 32 | }, 33 | "keywords": [ 34 | "smtp", 35 | "email", 36 | "verification" 37 | ], 38 | "author": "Pedro Ladeira (https://github.com/pepeladeira)", 39 | "contributors": [ 40 | "Daniel Bastos (https://github.com/danielsalles)" 41 | ], 42 | "license": "MIT", 43 | "bugs": { 44 | "url": "https://github.com/getpingback/ping-email/issues" 45 | }, 46 | "homepage": "https://github.com/getpingback/ping-email#readme", 47 | "devDependencies": { 48 | "@types/disposable-email": "^0.2.2", 49 | "@types/jest": "^29.5.12", 50 | "@types/node": "^20.11.24", 51 | "disposable-email": "^0.2.3", 52 | "jest": "^29.7.0", 53 | "ts-jest": "^29.1.2", 54 | "ts-node": "^10.9.2", 55 | "tsup": "^8.0.2", 56 | "typescript": "^5.3.3" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![ping-email Cover](https://res.cloudinary.com/pingback/image/upload/v1709848522/assets/general/cover_e4tjvr.png) 2 | 3 |
ping-email
4 |
Node.js SMTP Email Verification Library
5 | 6 | ## Install 7 | 8 | ```bash 9 | npm install ping-email 10 | # or 11 | yarn add ping-email 12 | ``` 13 | 14 | ## Setup 15 | 16 | ```js 17 | import { PingEmail } from "ping-email"; 18 | const pingEmail = new PingEmail({ 19 | port: 25, // Default SMTP port 20 | fqdn: "mail.example.org", // Fully Qualified Domain Name of your SMTP server 21 | sender: "name@example.org", // Email address to use as the sender in SMTP checks, 22 | timeout: 10000, // Time in milliseconds to wait for a response from the SMTP server 23 | attempts: 3, // Number of attempts to verify the email address 24 | }); 25 | ``` 26 | 27 | ## Usage 28 | 29 | ### Verifying an Email Address 30 | 31 | To verify an email address using `ping-email`, call the ping method with the target email address: 32 | 33 | ```js 34 | const { email, valid, message } = await pingEmail.ping("test@example.com"); 35 | 36 | if (valid) { 37 | console.log("Email is valid:", email); 38 | } else { 39 | console.error("Verification failed:", message); 40 | } 41 | ``` 42 | 43 | ### Ping Response 44 | 45 | The ping method returns an object with the following properties: 46 | 47 | - `email`: The email address being verified. 48 | - `valid`: A boolean indicating the overall validity of the email based on syntax, domain, and SMTP checks. 49 | - `success`: A boolean indicating if the verification process executed without encountering system-level errors (e.g., network issues). 50 | - `message`: A string providing detailed feedback about the verification outcome. This message can be one of the following, as defined in `PingResponseMessages`: 51 | - `"Valid email"`: The email address is valid. 52 | - `"Invalid email"`: The email address is invalid. 53 | - `"Valid domain"`: The domain of the email address is valid. 54 | - `"Invalid domain"`: The domain of the email address is invalid. 55 | - `"Email is required"`: No email address was provided for verification. 56 | - `"No MX records found"`: The domain does not have MX records, indicating it cannot receive emails. 57 | - `"Invalid email syntax"`: The email address provided does not meet the syntactical standards for email addresses. 58 | - `"SMTP connection error"`: There was an error connecting to the SMTP server for verification. 59 | - `"Disposable email is not allowed"`: The email address belongs to a disposable email provider. 60 | - `"Domain verification failed"`: The domain verification process failed. 61 | - `"Unable to verify email"`: The email verification process failed for an unknown reason. 62 | - `"Connection timeout"`: The connection to the SMTP server timed out. 63 | - `"Exceeded attempts"`: The maximum number of attempts to verify the email address was exceeded. 64 | - `"Valid email (ignored SMTP verification)"`: The email address is valid, but the SMTP verification process was skipped. 65 | 66 | These messages provide clear insights into the verification process, helping you understand the specific reason for an email's validation outcome. 67 | 68 | ### Error Handling 69 | 70 | When integrating `ping-email` into your applications, pay special attention to the success and message properties in the ping method response. They are key to identifying and handling different scenarios, such as invalid email syntax, domain issues, or SMTP server connectivity problems. Logging these details can be helpful for debugging purposes or improving user feedback in your application interface. 71 | 72 | ### Options 73 | 74 | You can customize `ping-email` by providing different options when you instantiate it. The available options are: 75 | 76 | - `port`: The port number to connect to the SMTP server `(default: 25)`. 77 | - `attempts`: The number of attempts to verify the email address `(default: 3)`. 78 | - `fqdn`: The Fully Qualified Domain Name of your SMTP server `(default: "mail.example.org")`. 79 | - `sender`: The email address used as the sender in SMTP checks `(default: "name@example.org")`. 80 | - `timeout`: The time in milliseconds to wait for a response from the SMTP server `(default: 10000)`. 81 | - `ignoreSMTPVerify`: A boolean indicating whether to skip the SMTP verification process `(default: false)`. 82 | - `debug`: A boolean indicating whether to enable debug mode, which logs detailed information about the verification process `(default: false)`. 83 | 84 | This allows you to tailor the library to your specific requirements, ensuring compatibility with your email verification workflow. 85 | 86 | ### Example 87 | 88 | Here's a complete example demonstrating how to verify an email address: 89 | 90 | ```js 91 | import { PingEmail } from "ping-email"; 92 | 93 | const pingEmail = new PingEmail({ 94 | port: 587, 95 | fqdn: "smtp.example.org", 96 | sender: "verify@example.org", 97 | timeout: 15000, 98 | attempts: 5, 99 | }); 100 | 101 | const { email, valid, success, message } = await pingEmail.ping("user@example.com"); 102 | ``` 103 | 104 | ## Understanding Email Verification 105 | 106 | ### What is SMTP? 107 | 108 | Simple Mail Transfer Protocol (SMTP) is the standard protocol for sending emails across the Internet. It defines the rules for how email messages are transmitted between mail servers, and how users' email clients submit outgoing emails to their outgoing mail server. 109 | 110 | ### Why Validate Email Addresses? 111 | 112 | Validating email addresses is crucial for several reasons: 113 | 114 | - **Reduce Bounce Rates**: Ensuring that emails are sent to valid addresses prevents emails from being bounced back. 115 | - **Improve Delivery Rates**: By filtering out invalid addresses, email campaigns can achieve higher delivery rates. 116 | - **Enhance Security**: Verification helps prevent fraud and enhances the security of email communication by ensuring that emails are sent to intended recipients. 117 | - **Save Resources**: By avoiding sending emails to non-existent or disposable addresses, businesses can save on server resources and focus on genuine users. 118 | 119 | ### Understanding Key Terms 120 | 121 | - **Port**: In the context of SMTP, a port is a numerical designation that specifies a specific gateway for network communication. Common SMTP ports include 25 (default SMTP), 587 (for encrypted SMTP), and 465 (SMTPS). 122 | 123 | - **FQDN (Fully Qualified Domain Name)**: This refers to the complete domain name of an Internet resource. In SMTP settings, it specifies the domain name of the SMTP server that is used to send emails. For example, `smtp.example.com`. 124 | 125 | - **Sender**: The email address that appears in the 'From' field of an email. In email verification, it's used to simulate the sending process without actually sending an email, helping to verify the validity of the recipient's address. 126 | 127 | Understanding these concepts is crucial for effectively utilizing `ping-email` and comprehending the mechanics of email verification and delivery. 128 | 129 | ## Risk of IP Blocking 130 | 131 | When using `ping-email` for SMTP email verification, there is a risk of IP blocking. This risk arises because the method employed by the library to verify email addresses can be perceived as suspicious by email servers, especially when requests are frequent or in large volumes. Email servers may interpret these verification attempts as spam or malicious activity, leading to the blocking of your IP address. 132 | 133 | ### Mitigating the Risk 134 | 135 | To mitigate this risk, it is crucial to use the library responsibly and understand the implications of SMTP email verification. If you are conducting bulk email verifications or are concerned about the potential for IP blocking, consider the following guidelines: 136 | 137 | - **Rate Limiting**: Limit the number of requests you make to the SMTP server within a given time frame. 138 | - **Use a Proxy**: Consider using a proxy server to distribute requests across multiple IP addresses, reducing the risk of being blocked. 139 | - **Use a Dedicated IP**: If you have a high volume of email verification requests, consider using a dedicated IP address to avoid being blocked. 140 | 141 | ### `ignoreSMTPVerify` Option 142 | 143 | The `ignoreSMTPVerify` option in `ping-email` allows you to skip the SMTP verification process, which can help reduce the risk of IP blocking. However, it is important to note that by doing so, you may miss out on valuable insights into the validity of email addresses and the integrity of the recipient's domain. 144 | 145 | Here's an example of how to use the `ignoreSMTPVerify` option: 146 | 147 | ```js 148 | const pingEmail = new PingEmail({ 149 | ignoreSMTPVerify: true, 150 | }); 151 | ``` 152 | 153 | ### Best Practices 154 | 155 | - **Gradual Processing**: Gradually process large lists of email addresses to avoid sudden spikes in traffic that could lead to your IP being flagged. 156 | - **Monitoring and Feedback**: Regularly monitor the feedback from the servers you are verifying against. If you notice an increase in failures or blocks, adjust your verification strategy accordingly. 157 | - **Compliance and Ethics**: Always use email verification tools ethically and in compliance with internet standards and regulations to maintain a good sending reputation. 158 | 159 | By following these guidelines and using `ping-email` judiciously, you can effectively verify email addresses while minimizing the risk of IP blocking and maintaining the integrity of your email verification processes. 160 | 161 | --- 162 | 163 | ## Authors 164 | 165 | - Pedro Ladeira ([@pedrooladeira](https://twitter.com/pedrooladeira)) 166 | - Daniel Bastos ([@dannnnnnnn\_\_\_](https://twitter.com/dannnnnnnn___)) 167 | 168 | --- 169 | 170 | ## Credits 171 | 172 | This library, `ping-email`, was inspired by the [`email-verify`](https://github.com/EmailVerify/email-verify) project. We extend our sincere gratitude to the creators and contributors of `email-verify` for their innovative work in the field of email verification. 173 | 174 | --- 175 | 176 | ## Community 177 | 178 | ### Newsroom 179 | 180 | We are always posting about our latest news and updates on our Newsroom. Check it out! 181 | 182 | [Visit Pingback Newsroom](https://pingback.com/newsroom) 183 | 184 | ### Twitter 185 | 186 | To receive updates about our Engineering team, follow along on Twitter. 187 | 188 | [Follow Pingback on Twitter](https://twitter.com/pingbackoficial) 189 | 190 | --- 191 | 192 | ## License 193 | 194 | Licensed under the MIT License, Copyright © 2024-present [Pingback](https://pingback.com/). 195 | 196 | See [LICENSE](./LICENSE) for more information. 197 | -------------------------------------------------------------------------------- /src/emails/emails.spec.ts: -------------------------------------------------------------------------------- 1 | import { Emails } from "./emails"; 2 | import { 3 | PingEmailOptions, 4 | PingResponseMessages, 5 | } from "../interfaces/ping-email.interface"; 6 | 7 | const mockOptions = { 8 | port: 25, 9 | fqdn: "mail.example.org", 10 | sender: "name@example.org", 11 | timeout: 10000, 12 | debug: false, 13 | attempts: 3, 14 | } as PingEmailOptions; 15 | 16 | const emails = new Emails(mockOptions); 17 | 18 | describe("Emails", () => { 19 | const validEmail = "pmladeira36@gmail.com"; 20 | const invalidEmail = "p@jzbcsajkbakas.com"; 21 | const invalidSyntaxEmail = "p@gmail"; 22 | const disposableEmail = "p@tempemail.com"; 23 | 24 | describe("verifySyntax", () => { 25 | it("should return true if email syntax is valid", () => { 26 | const result = emails.verifySyntax(validEmail); 27 | expect(result).toBe(true); 28 | }); 29 | 30 | it("should return false if email syntax is invalid", () => { 31 | const result = emails.verifySyntax(invalidSyntaxEmail); 32 | expect(result).toBe(false); 33 | }); 34 | }); 35 | 36 | describe("verifyDisposableDomain", () => { 37 | it("should return false if email domain is not disposable", () => { 38 | const result = emails.verifyDisposableDomain(validEmail); 39 | expect(result).toBe(false); 40 | }); 41 | 42 | it("should return true if email domain is disposable", () => { 43 | const result = emails.verifyDisposableDomain(disposableEmail); 44 | expect(result).toBe(true); 45 | }); 46 | }); 47 | 48 | describe("verifyDomain", () => { 49 | it("should return VALID_DOMAIN if domain is valid", async () => { 50 | const result = await emails.verifyDomain(validEmail); 51 | 52 | const expected = { 53 | smtp: "gmail-smtp-in.l.google.com", 54 | valid: true, 55 | foundMx: true, 56 | message: PingResponseMessages.VALID_DOMAIN, 57 | }; 58 | 59 | expect(result).toEqual(expected); 60 | }); 61 | 62 | it("should return DOMAIN_VERIFICATION_FAILED if domain is invalid", async () => { 63 | const result = await emails.verifyDomain(invalidEmail); 64 | 65 | const expected = { 66 | valid: false, 67 | foundMx: false, 68 | message: PingResponseMessages.DOMAIN_VERIFICATION_FAILED, 69 | }; 70 | 71 | expect(result).toEqual(expected); 72 | }); 73 | }); 74 | 75 | describe("verifySMTP", () => { 76 | const smtp = "gmail-smtp-in.l.google.com"; 77 | 78 | it("should return valid response if SMTP is valid", async () => { 79 | const result = await emails.verifySMTP(validEmail, smtp); 80 | 81 | const expected = { 82 | valid: true, 83 | tryAgain: false, 84 | success: true, 85 | message: PingResponseMessages.VALID, 86 | }; 87 | 88 | expect(result).toEqual(expected); 89 | }); 90 | 91 | it("should return invalid response if SMTP is invalid", async () => { 92 | const result = await emails.verifySMTP(disposableEmail, smtp); 93 | 94 | const expected = { 95 | valid: false, 96 | tryAgain: false, 97 | success: true, 98 | message: PingResponseMessages.INVALID, 99 | }; 100 | 101 | expect(result).toEqual(expected); 102 | }); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /src/emails/emails.ts: -------------------------------------------------------------------------------- 1 | import { resolveMx } from "dns"; 2 | import { promisify } from "util"; 3 | import { createConnection } from "net"; 4 | import disposable from "disposable-email"; 5 | 6 | import { 7 | PingEmailOptions, 8 | PingResponseMessages, 9 | } from "../interfaces/ping-email.interface"; 10 | import { 11 | VerifyDomainResponse, 12 | VerifySMTPResponse, 13 | } from "../interfaces/emails.interface"; 14 | import { Log } from "../log/log"; 15 | 16 | const resolveMxPromise = promisify(resolveMx); 17 | 18 | class Emails { 19 | private readonly log: Log; 20 | 21 | constructor(readonly options: PingEmailOptions) { 22 | this.options = options; 23 | this.log = new Log(this.options.debug); 24 | } 25 | 26 | verifySyntax(email: string): boolean { 27 | const regex: RegExp = 28 | /^(([^<>()\[\]\\.,;:\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,}))$/; 29 | return regex.test(email.toLowerCase()); 30 | } 31 | 32 | verifyDisposableDomain(email: string): boolean { 33 | return !disposable.validate(email); 34 | } 35 | 36 | async verifyDomain(email: string): Promise { 37 | const domain: string = email.split("@")[1].toLocaleLowerCase(); 38 | 39 | try { 40 | const addresses = await resolveMxPromise(domain); 41 | 42 | if (addresses && addresses.length > 0) { 43 | let priority = 10000; 44 | let lowestPriorityIndex = 0; 45 | 46 | addresses.forEach((address, index) => { 47 | if (address.priority < priority) { 48 | priority = address.priority; 49 | lowestPriorityIndex = index; 50 | } 51 | }); 52 | 53 | const smtp = addresses[lowestPriorityIndex].exchange; 54 | 55 | return { 56 | smtp, 57 | valid: true, 58 | foundMx: true, 59 | message: PingResponseMessages.VALID_DOMAIN, 60 | }; 61 | } else { 62 | return { 63 | valid: false, 64 | foundMx: false, 65 | message: PingResponseMessages.NO_MX_RECORDS, 66 | }; 67 | } 68 | } catch (err) { 69 | return { 70 | valid: false, 71 | foundMx: false, 72 | message: PingResponseMessages.DOMAIN_VERIFICATION_FAILED, 73 | }; 74 | } 75 | } 76 | 77 | async verifySMTP(email: string, smtp: string): Promise { 78 | return new Promise((resolve) => { 79 | let stage = 0; 80 | let banner = ""; 81 | let response = ""; 82 | let ended = false; 83 | let success = false; 84 | let tryAgain = false; 85 | let completed = false; 86 | 87 | const connection = createConnection(this.options.port, smtp); 88 | 89 | connection.setTimeout(this.options.timeout, () => { 90 | resolve({ 91 | tryAgain, 92 | valid: false, 93 | success: false, 94 | message: PingResponseMessages.CONNECTION_TIMEOUT, 95 | }); 96 | 97 | connection.destroy(); 98 | }); 99 | 100 | connection.on("data", (data) => { 101 | response += data.toString(); 102 | completed = response.slice(-1) === "\n"; 103 | 104 | if (completed) { 105 | this.log.info(`SMTP Response: ${response}`); 106 | 107 | switch (stage) { 108 | case 0: 109 | if (response.indexOf("220") > -1 && !ended) { 110 | banner = response; 111 | const cmd = `EHLO ${this.options.fqdn}\r\n`; 112 | 113 | this.log.info(`SMTP Command: ${cmd}`); 114 | 115 | connection.write(cmd, () => { 116 | stage++; 117 | response = ""; 118 | }); 119 | } else { 120 | if ( 121 | response.indexOf("421") > -1 || 122 | response.indexOf("450") > -1 || 123 | response.indexOf("451") > -1 124 | ) 125 | tryAgain = true; 126 | connection.end(); 127 | } 128 | break; 129 | case 1: 130 | if (response.indexOf("250") > -1 && !ended) { 131 | const cmd = `MAIL FROM:<${this.options.sender}>\r\n`; 132 | this.log.info(`SMTP Command: ${cmd}`); 133 | 134 | connection.write(cmd, () => { 135 | stage++; 136 | response = ""; 137 | }); 138 | } else { 139 | connection.end(); 140 | } 141 | break; 142 | case 2: 143 | if (response.indexOf("250") > -1 && !ended) { 144 | const cmd = `RCPT TO:<${email}>\r\n`; 145 | 146 | this.log.info(`SMTP Command: ${cmd}`); 147 | 148 | connection.write(cmd, () => { 149 | stage++; 150 | response = ""; 151 | }); 152 | } else { 153 | connection.end(); 154 | } 155 | break; 156 | case 3: 157 | if ( 158 | response.indexOf("250") > -1 || 159 | ("405" && response.indexOf("405") > -1) 160 | ) { 161 | success = true; 162 | } 163 | stage++; 164 | response = ""; 165 | 166 | if (!ended) { 167 | const cmd = "QUIT\r\n"; 168 | 169 | this.log.info(`SMTP Command: ${cmd}`); 170 | 171 | connection.write(cmd); 172 | } 173 | break; 174 | case 4: 175 | connection.end(); 176 | } 177 | } 178 | }); 179 | 180 | connection.once("connect", () => { 181 | this.log.info(`Connection to SMTP server established`); 182 | }); 183 | 184 | connection.once("error", (err) => { 185 | this.log.error(`Error connecting to SMTP server: ${err}`); 186 | 187 | resolve({ 188 | tryAgain, 189 | valid: false, 190 | success: false, 191 | message: PingResponseMessages.SMTP_CONNECTION_ERROR, 192 | }); 193 | }); 194 | 195 | connection.once("end", () => { 196 | this.log.info(`Connection to SMTP server ended`); 197 | 198 | if (success) { 199 | resolve({ 200 | tryAgain, 201 | valid: true, 202 | success: true, 203 | message: PingResponseMessages.VALID, 204 | }); 205 | } else { 206 | resolve({ 207 | tryAgain, 208 | valid: false, 209 | success: true, 210 | message: PingResponseMessages.INVALID, 211 | }); 212 | } 213 | }); 214 | }); 215 | } 216 | } 217 | 218 | export { Emails }; 219 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { PingEmail } from "./ping-email"; 2 | -------------------------------------------------------------------------------- /src/interfaces/emails.interface.ts: -------------------------------------------------------------------------------- 1 | import { PingResponseMessages } from "./ping-email.interface"; 2 | 3 | export interface VerifyDomainResponse { 4 | smtp?: string; 5 | valid: boolean; 6 | foundMx: boolean; 7 | message: PingResponseMessages; 8 | } 9 | 10 | export interface VerifySMTPResponse { 11 | valid: boolean; 12 | success: boolean; 13 | tryAgain: boolean; 14 | message: PingResponseMessages; 15 | } 16 | -------------------------------------------------------------------------------- /src/interfaces/ping-email.interface.ts: -------------------------------------------------------------------------------- 1 | export enum PingResponseMessages { 2 | VALID = "Valid email", 3 | VALID_IGNORED_SMTP = "Valid email (ignored SMTP verification)", 4 | INVALID = "Invalid email", 5 | VALID_DOMAIN = "Valid domain", 6 | INVALID_DOMAIN = "Invalid domain", 7 | EMAIL_REQUIRED = "Email is required", 8 | NO_MX_RECORDS = "No MX records found", 9 | INVALID_SYNTAX = "Invalid email syntax", 10 | SMTP_CONNECTION_ERROR = "SMTP connection error", 11 | DISPOSABLE_EMAIL = "Disposable email is not allowed", 12 | DOMAIN_VERIFICATION_FAILED = "Domain verification failed", 13 | UNABLE_TO_VERIFY = "Unable to verify email", 14 | CONNECTION_TIMEOUT = "Connection timeout", 15 | ATTEMPTS_EXCEEDED = "Exceeded attempts", 16 | } 17 | 18 | export interface PingEmailConstructorOptions { 19 | port?: number; 20 | fqdn?: string; 21 | sender?: string; 22 | debug?: boolean; 23 | timeout?: number; 24 | attempts?: number; 25 | ignoreSMTPVerify?: boolean; 26 | } 27 | 28 | export interface PingEmailOptions { 29 | port: number; 30 | fqdn: string; 31 | sender: string; 32 | debug: boolean; 33 | timeout: number; 34 | attempts: number; 35 | ignoreSMTPVerify: boolean; 36 | } 37 | 38 | export interface PingResponse { 39 | email: string; 40 | valid: boolean; 41 | success: boolean; 42 | message: PingResponseMessages; 43 | } 44 | -------------------------------------------------------------------------------- /src/log/log.spec.ts: -------------------------------------------------------------------------------- 1 | import { Log } from "./log"; 2 | 3 | describe("Log", () => { 4 | let consoleSpy: jest.SpyInstance; 5 | 6 | beforeEach(() => { 7 | consoleSpy = jest.spyOn(console, "log"); 8 | consoleSpy.mockImplementation(() => {}); 9 | }); 10 | 11 | afterEach(() => { 12 | consoleSpy.mockRestore(); 13 | }); 14 | 15 | describe("in debug mode", () => { 16 | const debugMode = true; 17 | let logger: Log; 18 | 19 | beforeEach(() => { 20 | logger = new Log(debugMode); 21 | }); 22 | 23 | it("should log info messages", () => { 24 | const message = "Test info message"; 25 | logger.info(message); 26 | 27 | expect(consoleSpy).toHaveBeenCalledWith( 28 | "\x1b[34m", 29 | `[ping-email - INFO]: ${message}` 30 | ); 31 | }); 32 | 33 | it("should log error messages", () => { 34 | const message = "Test error message"; 35 | logger.error(message); 36 | 37 | expect(consoleSpy).toHaveBeenCalledWith( 38 | "\x1b[31m", 39 | `[ping-email - ERROR]: ${message}` 40 | ); 41 | }); 42 | }); 43 | 44 | describe("in non-debug mode", () => { 45 | const debugMode = false; 46 | let logger: Log; 47 | 48 | beforeEach(() => { 49 | logger = new Log(debugMode); 50 | }); 51 | 52 | it("should not log info messages", () => { 53 | logger.info("Test info message"); 54 | expect(consoleSpy).not.toHaveBeenCalled(); 55 | }); 56 | 57 | it("should not log error messages", () => { 58 | logger.error("Test error message"); 59 | expect(consoleSpy).not.toHaveBeenCalled(); 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /src/log/log.ts: -------------------------------------------------------------------------------- 1 | class Log { 2 | private debug: boolean; 3 | 4 | constructor(debug: boolean = false) { 5 | this.debug = debug; 6 | } 7 | 8 | info(message: string): void { 9 | if (this.debug) 10 | console.log("\x1b[34m", `[ping-email - INFO]: ${message}`); 11 | } 12 | 13 | error(message: string): void { 14 | if (this.debug) 15 | console.log("\x1b[31m", `[ping-email - ERROR]: ${message}`); 16 | } 17 | } 18 | 19 | export { Log }; 20 | -------------------------------------------------------------------------------- /src/ping-email.spec.ts: -------------------------------------------------------------------------------- 1 | import { PingResponseMessages } from "./interfaces/ping-email.interface"; 2 | import { PingEmail } from "./ping-email"; 3 | 4 | describe("PingEmail", () => { 5 | describe("constructor", () => { 6 | it("should create a new instance of PingEmail", () => { 7 | const pingEmail = new PingEmail(); 8 | expect(pingEmail).toBeInstanceOf(PingEmail); 9 | }); 10 | 11 | it("should create a new instance of PingEmail with options", () => { 12 | const options = { 13 | port: 25, 14 | debug: true, 15 | timeout: 10000, 16 | sender: "a@a.com", 17 | fqdn: "a.com", 18 | attempts: 3, 19 | }; 20 | 21 | const pingEmail = new PingEmail(options); 22 | expect(pingEmail).toBeInstanceOf(PingEmail); 23 | }); 24 | }); 25 | 26 | describe("ping", () => { 27 | const validEmail = "pmladeira36@gmail.com"; 28 | const invalidEmail = "p@gmail.com"; 29 | const invalidDomain = "p@jzbcsajkbakas.com"; 30 | const invalidSyntaxEmail = "p@gmail"; 31 | const disposableEmail = "p@tempemail.com"; 32 | 33 | it("should return an error when email is not provided", async () => { 34 | const pingEmail = new PingEmail(); 35 | const response = await pingEmail.ping(""); 36 | expect(response.valid).toBe(false); 37 | expect(response.success).toBe(true); 38 | expect(response.message).toBe(PingResponseMessages.EMAIL_REQUIRED); 39 | }); 40 | 41 | it("should return an error when email syntax is invalid", async () => { 42 | const pingEmail = new PingEmail(); 43 | const response = await pingEmail.ping(invalidSyntaxEmail); 44 | expect(response.valid).toBe(false); 45 | expect(response.success).toBe(true); 46 | expect(response.message).toBe(PingResponseMessages.INVALID_SYNTAX); 47 | }); 48 | 49 | it("should return an error when email domain is disposable", async () => { 50 | const pingEmail = new PingEmail(); 51 | const response = await pingEmail.ping(disposableEmail); 52 | 53 | expect(response.valid).toBe(false); 54 | expect(response.success).toBe(true); 55 | expect(response.message).toBe(PingResponseMessages.DISPOSABLE_EMAIL); 56 | }); 57 | 58 | it("should return an error when email domain is invalid", async () => { 59 | const pingEmail = new PingEmail(); 60 | const response = await pingEmail.ping(invalidDomain); 61 | 62 | expect(response.valid).toBe(false); 63 | expect(response.success).toBe(true); 64 | expect(response.message).toBe(PingResponseMessages.DOMAIN_VERIFICATION_FAILED); 65 | }); 66 | 67 | it("should return a success when email is valid", async () => { 68 | const pingEmail = new PingEmail(); 69 | const response = await pingEmail.ping(validEmail); 70 | 71 | expect(response.valid).toBe(true); 72 | expect(response.success).toBe(true); 73 | expect(response.message).toBe(PingResponseMessages.VALID); 74 | }); 75 | 76 | it("should return an error when email is invalid", async () => { 77 | const pingEmail = new PingEmail(); 78 | const response = await pingEmail.ping(invalidEmail); 79 | 80 | expect(response.valid).toBe(false); 81 | expect(response.success).toBe(true); 82 | expect(response.message).toBe(PingResponseMessages.INVALID); 83 | }); 84 | 85 | it("should ignore SMTP verification when ignoreSMTPVerify is true", async () => { 86 | const pingEmail = new PingEmail({ ignoreSMTPVerify: true }); 87 | const response = await pingEmail.ping(validEmail); 88 | 89 | expect(response.valid).toBe(true); 90 | expect(response.success).toBe(true); 91 | expect(response.message).toBe(PingResponseMessages.VALID_IGNORED_SMTP); 92 | }); 93 | }); 94 | 95 | describe("pingBatch", () => { 96 | const validEmail1 = "pmladeira36@gmail.com"; 97 | const validEmail2 = "pmladeira36@gmail.com"; 98 | const invalidEmail = "invalid@example.com"; 99 | const disposableEmail = "p@tempemail.com"; 100 | const invalidSyntaxEmail = "invalidsyntax@.com"; 101 | 102 | it("should verify a batch of valid emails", async () => { 103 | const pingEmail = new PingEmail(); 104 | const emails = [validEmail1, validEmail2]; 105 | const results = await pingEmail.pingBatch(emails); 106 | 107 | expect(results).toHaveLength(2); 108 | results.forEach((result) => { 109 | expect(result.valid).toBe(true); 110 | expect(result.success).toBe(true); 111 | expect(result.message).toBe(PingResponseMessages.VALID); 112 | }); 113 | }); 114 | 115 | it("should handle a batch of mixed emails", async () => { 116 | const pingEmail = new PingEmail(); 117 | const emails = [validEmail1, invalidEmail, disposableEmail, invalidSyntaxEmail]; 118 | const results = await pingEmail.pingBatch(emails); 119 | 120 | expect(results).toHaveLength(4); 121 | expect(results[0].valid).toBe(true); 122 | expect(results[1].valid).toBe(false); 123 | expect(results[2].valid).toBe(false); 124 | expect(results[3].valid).toBe(false); 125 | }); 126 | 127 | it("should respect the maximum batch size", async () => { 128 | const pingEmail = new PingEmail(); 129 | const emails = Array(49).fill(validEmail1); 130 | const results = await pingEmail.pingBatch(emails); 131 | 132 | expect(results).toHaveLength(49); 133 | }); 134 | 135 | it("should handle an empty batch", async () => { 136 | const pingEmail = new PingEmail(); 137 | const results = await pingEmail.pingBatch([]); 138 | 139 | expect(results).toHaveLength(0); 140 | }); 141 | 142 | it("should handle errors during verification", async () => { 143 | const pingEmail = new PingEmail(); 144 | jest.spyOn(pingEmail, "ping").mockRejectedValueOnce(new Error("Test error")); 145 | 146 | const emails = [validEmail1, validEmail2]; 147 | await expect(pingEmail.pingBatch(emails)).rejects.toThrow("Test error"); 148 | }); 149 | 150 | it("should ignore SMTP verification when configured", async () => { 151 | const pingEmail = new PingEmail({ ignoreSMTPVerify: true }); 152 | const emails = [validEmail1, validEmail2]; 153 | const results = await pingEmail.pingBatch(emails); 154 | 155 | expect(results).toHaveLength(2); 156 | results.forEach((result) => { 157 | expect(result.valid).toBe(true); 158 | expect(result.message).toBe(PingResponseMessages.VALID_IGNORED_SMTP); 159 | }); 160 | }); 161 | }); 162 | }); 163 | -------------------------------------------------------------------------------- /src/ping-email.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import { Log } from "./log/log"; 4 | import { Emails } from "./emails/emails"; 5 | import { PingResponse, PingEmailOptions, PingResponseMessages, PingEmailConstructorOptions } from "./interfaces/ping-email.interface"; 6 | 7 | class PingEmail { 8 | private readonly log: Log; 9 | private readonly emails: Emails; 10 | private readonly options: PingEmailOptions; 11 | 12 | constructor(options?: PingEmailConstructorOptions) { 13 | this.options = { 14 | port: options?.port || 25, 15 | debug: options?.debug || false, 16 | timeout: options?.timeout || 10000, 17 | sender: options?.sender || "name@example.org", 18 | fqdn: options?.fqdn || "mail.example.org", 19 | attempts: options?.attempts || 3, 20 | ignoreSMTPVerify: options?.ignoreSMTPVerify || false, 21 | }; 22 | 23 | this.log = new Log(this.options.debug); 24 | this.emails = new Emails(this.options); 25 | } 26 | 27 | async ping(email: string): Promise { 28 | this.log.info(`Pinging email: ${email}`); 29 | 30 | if (!email) { 31 | return { 32 | email, 33 | valid: false, 34 | success: true, 35 | message: PingResponseMessages.EMAIL_REQUIRED, 36 | }; 37 | } 38 | 39 | this.log.info(`Verifying syntax of email: ${email}`); 40 | const isSyntaxValid = this.emails.verifySyntax(email); 41 | if (!isSyntaxValid) { 42 | return { 43 | email, 44 | valid: false, 45 | success: true, 46 | message: PingResponseMessages.INVALID_SYNTAX, 47 | }; 48 | } 49 | 50 | this.log.info(`Verifying disposable domain of email: ${email}`); 51 | const isDisposable = this.emails.verifyDisposableDomain(email); 52 | if (isDisposable) { 53 | return { 54 | email, 55 | valid: false, 56 | success: true, 57 | message: PingResponseMessages.DISPOSABLE_EMAIL, 58 | }; 59 | } 60 | 61 | this.log.info(`Verifying domain of email: ${email}`); 62 | const { smtp, foundMx, valid: isDomainValid, message: domainMessage } = await this.emails.verifyDomain(email); 63 | if (!isDomainValid) { 64 | return { 65 | email, 66 | valid: false, 67 | success: true, 68 | message: domainMessage, 69 | }; 70 | } 71 | 72 | if (this.options.ignoreSMTPVerify && foundMx && smtp) { 73 | this.log.info(`Ignoring SMTP verification of email: ${email}`); 74 | return { 75 | email, 76 | valid: true, 77 | success: true, 78 | message: PingResponseMessages.VALID_IGNORED_SMTP, 79 | }; 80 | } 81 | 82 | this.log.info(`Verifying SMTP of email: ${email}`); 83 | if (isDomainValid && foundMx && smtp) { 84 | for (let i = 0; i < this.options.attempts; i++) { 85 | this.log.info(`Attempt ${i + 1} of ${this.options.attempts}`); 86 | 87 | const { valid, success, message, tryAgain } = await this.emails.verifySMTP(email, smtp); 88 | 89 | if (success) { 90 | return { 91 | email, 92 | valid, 93 | success, 94 | message, 95 | }; 96 | } 97 | 98 | if (!tryAgain) { 99 | return { 100 | email, 101 | valid, 102 | success, 103 | message, 104 | }; 105 | } 106 | } 107 | 108 | this.log.info(`Attempts exceeded for email: ${email}`); 109 | return { 110 | email, 111 | valid: false, 112 | success: false, 113 | message: PingResponseMessages.ATTEMPTS_EXCEEDED, 114 | }; 115 | } 116 | 117 | return { 118 | email, 119 | valid: false, 120 | success: false, 121 | message: PingResponseMessages.UNABLE_TO_VERIFY, 122 | }; 123 | } 124 | 125 | async pingBatch(emails: string[], batchSize: number = 50): Promise { 126 | this.log.info(`Starting batch verification of ${emails.length} emails`); 127 | 128 | if (emails.length > batchSize) { 129 | this.log.error(`The number of emails exceeds the maximum batch size. Limiting to ${batchSize} emails.`); 130 | emails = emails.slice(0, batchSize); 131 | } 132 | 133 | const pingPromises = emails.map(email => this.ping(email)); 134 | 135 | try { 136 | const results = await Promise.all(pingPromises); 137 | this.log.info(`Batch verification completed for ${results.length} emails`); 138 | return results; 139 | } catch (error) { 140 | this.log.error(`Error during batch verification: ${error}`); 141 | throw error; 142 | } 143 | } 144 | } 145 | 146 | export { PingEmail }; 147 | -------------------------------------------------------------------------------- /src/types/disposable-email.d.ts: -------------------------------------------------------------------------------- 1 | declare module "disposable-email" { 2 | export function validate(domainOrEmail: string): boolean; 3 | export function validate( 4 | domainOrEmail: string, 5 | callback: (error: null, isValid: boolean) => void 6 | ): void; 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs" /* Specify what module code is generated. */, 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 39 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 42 | // "resolveJsonModule": true, /* Enable importing .json files. */ 43 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 44 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 45 | 46 | /* JavaScript Support */ 47 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 48 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 49 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 50 | 51 | /* Emit */ 52 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 53 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 54 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 55 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 56 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 57 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 58 | "outDir": "./dist" /* Specify an output folder for all emitted files. */, 59 | // "removeComments": true, /* Disable emitting comments. */ 60 | // "noEmit": true, /* Disable emitting files from a compilation. */ 61 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 62 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 66 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 67 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 68 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 69 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 70 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 71 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 72 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 73 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 74 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 75 | 76 | /* Interop Constraints */ 77 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 78 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 79 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 80 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, 81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 82 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 83 | 84 | /* Type Checking */ 85 | "strict": true /* Enable all strict type-checking options. */, 86 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 87 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 88 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 89 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 90 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 91 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 92 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 93 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 94 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 95 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 96 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 97 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 98 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 99 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 100 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 101 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 102 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 103 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 104 | 105 | /* Completeness */ 106 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 107 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 108 | } 109 | } 110 | --------------------------------------------------------------------------------