├── .eslintrc
├── .eslintrc.json
├── .github
└── workflows
│ └── node.js.yml
├── .gitignore
├── .npmignore
├── .npmrc
├── LICENSE
├── README.md
├── examples
├── account-info.ts
├── error-handling.ts
├── jobs-create.ts
├── jobs-delete.ts
├── jobs-download.ts
├── jobs-parse.ts
├── jobs-results.ts
├── jobs-search.ts
├── jobs-start.ts
├── jobs-status.ts
├── poe-confirm.ts
└── single-check.ts
├── jest.config.cjs
├── package-lock.json
├── package.json
├── src
├── Account.ts
├── Errors.ts
├── HttpsClient.ts
├── Jobs.ts
├── NeverBounce.ts
├── POE.ts
├── Single.ts
├── VerificationObject.ts
├── index.ts
└── types.ts
├── test-cjs.cjs
├── test-esm.js
├── tests
├── Account.test.ts
├── Errors.test.ts
├── Jobs.test.ts
├── NeverBounce.test.ts
├── POE.test.ts
├── Single.test.ts
├── VerificationObject.test.ts
├── setup.ts
└── tsconfig.json
├── tsconfig.cjs.json
└── tsconfig.json
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "es6": true,
4 | "node": true
5 | },
6 | "rules": {
7 | "quotes": [2, "single"],
8 | "comma-dangle": 0,
9 | "indent": [2, 4, {"SwitchCase": 1}],
10 | "no-console": 0,
11 | "no-alert": 0
12 | }
13 | }
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@typescript-eslint/parser",
3 | "extends": [
4 | "eslint:recommended",
5 | "plugin:@typescript-eslint/recommended"
6 | ],
7 | "plugins": ["@typescript-eslint"],
8 | "env": {
9 | "node": true,
10 | "es2020": true,
11 | "mocha": true
12 | },
13 | "parserOptions": {
14 | "ecmaVersion": 2020,
15 | "sourceType": "module"
16 | },
17 | "rules": {
18 | "no-console": "warn",
19 | "@typescript-eslint/explicit-module-boundary-types": "warn",
20 | "@typescript-eslint/no-explicit-any": "warn"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | name: Node.js CI
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | pull_request:
7 | branches: [master]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | strategy:
14 | matrix:
15 | node-version: [20.x, 18.x]
16 |
17 | steps:
18 | - name: Checkout code
19 | uses: actions/checkout@v4
20 |
21 | - name: Setup Node.js ${{ matrix.node-version }}
22 | uses: actions/setup-node@v4
23 | with:
24 | node-version: ${{ matrix.node-version }}
25 | cache: 'npm'
26 |
27 | - name: Install dependencies
28 | run: npm ci
29 |
30 | - name: Run tests
31 | run: npm test
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Node template
3 | # Logs
4 | logs
5 | *.log
6 | npm-debug.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 |
13 | # Directory for instrumented libs generated by jscoverage/JSCover
14 | lib-cov
15 |
16 | # Coverage directory used by tools like istanbul
17 | coverage
18 |
19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
20 | .grunt
21 |
22 | # node-waf configuration
23 | .lock-wscript
24 |
25 | # Compiled binary addons (http://nodejs.org/api/addons.html)
26 | build/Release
27 |
28 | # Dependency directory
29 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
30 | node_modules
31 | ### SublimeText template
32 | # cache files for sublime text
33 | *.tmlanguage.cache
34 | *.tmPreferences.cache
35 | *.stTheme.cache
36 |
37 | # workspace files are user-specific
38 | *.sublime-workspace
39 |
40 | # project files should be checked into the repository, unless a significant
41 | # proportion of contributors will probably not be using SublimeText
42 | # *.sublime-project
43 |
44 | # sftp configuration file
45 | sftp-config.json
46 | ### JetBrains template
47 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
48 |
49 | *.iml
50 |
51 | ## Directory-based project format:
52 | .idea/
53 | # if you remove the above rule, at least ignore the following:
54 |
55 | # User-specific stuff:
56 | # .idea/workspace.xml
57 | # .idea/tasks.xml
58 | # .idea/dictionaries
59 |
60 | # Sensitive or high-churn files:
61 | # .idea/dataSources.ids
62 | # .idea/dataSources.xml
63 | # .idea/sqlDataSources.xml
64 | # .idea/dynamic.xml
65 | # .idea/uiDesigner.xml
66 |
67 | # Gradle:
68 | # .idea/gradle.xml
69 | # .idea/libraries
70 |
71 | # Mongo Explorer plugin:
72 | # .idea/mongoSettings.xml
73 |
74 | ## File-based project format:
75 | *.ipr
76 | *.iws
77 |
78 | ## Plugin-specific files:
79 |
80 | # IntelliJ
81 | /out/
82 |
83 | # mpeltonen/sbt-idea plugin
84 | .idea_modules/
85 |
86 | # JIRA plugin
87 | atlassian-ide-plugin.xml
88 |
89 | # Crashlytics plugin (for Android Studio and IntelliJ)
90 | com_crashlytics_export_strings.xml
91 | crashlytics.properties
92 | crashlytics-build.properties
93 |
94 | # TypeScript build output
95 | dist/
96 |
97 | # Environment files
98 | .env*
99 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | *.sublime-workspace
2 | .idea/
3 | .env*
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org/
2 | strict-ssl=false
3 | proxy=null
4 | https-proxy=null
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2017 NeverBounce
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |

2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | > Looking for the V4 API wrapper with JavaScript? Check out version 4.x.x.
11 |
12 | This is the official NeverBounce API NodeJS wrapper. It provides helpful methods to quickly implement our API in your NodeJS applications. Version 5.0.0 has been completely rewritten in TypeScript and requires Node.js 18 or higher
13 |
14 | **This package is not suitable for use in the browser! Only use it in server side applications!**
15 |
16 | ## Breaking Changes in 5.0.0
17 |
18 | - Requires Node.js 18 or higher
19 | - Converted to TypeScript with full type definitions
20 | - Uses ES Modules instead of CommonJS
21 | - Uses modern fetch API instead of https module
22 | - Improved error handling with proper TypeScript types
23 |
24 | ## Installation
25 |
26 | To install use the following command:
27 |
28 | ```bash
29 | $ npm install neverbounce --save
30 | ```
31 |
32 | ## Basic Usage
33 |
34 | >**The API username and secret key used to authenticate V3 API requests will not work to authenticate V4 API requests.** If you are attempting to authenticate your request with the 8 character username or 12-16 character secret key the request will return an `auth_failure` error. The API key used for the V4 API will look like the following: `secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`. To create new V4 API credentials please go [here](https://app.neverbounce.com/apps/custom-integration/new).
35 |
36 | ### Example with async/await
37 |
38 | ```ts
39 | import NeverBounce from 'neverbounce';
40 |
41 | // Initialize NeverBounce client
42 | const client = new NeverBounce({apiKey: 'secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'});
43 |
44 | // Verify an email with async/await
45 | async function verifyEmail() {
46 | try {
47 | // You can request additional information like address_info and credits_info
48 | const result = await client.single.check('support@neverbounce.com', true, true);
49 |
50 | console.log('Result: ' + result.getResult()); // prints: "valid"
51 | console.log('Result (numeric): ' + result.getNumericResult());
52 | console.log('Is Valid? ' + result.is(NeverBounce.result.valid));
53 |
54 | // Access the response data with proper typing
55 | const response = result.getResponse();
56 |
57 | if (response.address_info) {
58 | console.log('Host: ' + response.address_info.host);
59 | }
60 |
61 | if (response.credits_info) {
62 | console.log('Free Credits Used: ' + response.credits_info.free_credits_used);
63 | console.log('Paid Credits Used: ' + response.credits_info.paid_credits_used);
64 | }
65 | } catch (err) {
66 | // Errors are thrown and can be caught to handle specific error types
67 | if (err instanceof Error) {
68 | switch(err.type) {
69 | case NeverBounce.errors.AuthError:
70 | // The API credentials used are bad, have you reset them recently?
71 | break;
72 | case NeverBounce.errors.BadReferrerError:
73 | // The script is being used from an unauthorized source, you may need to
74 | // adjust your app's settings to allow it to be used from here
75 | break;
76 | case NeverBounce.errors.ThrottleError:
77 | // Too many requests in a short amount of time, try again shortly or adjust
78 | // your rate limit settings for this application in the dashboard
79 | break;
80 | case NeverBounce.errors.GeneralError:
81 | // A non recoverable API error occurred check the message for details
82 | break;
83 | default:
84 | // Other non specific errors
85 | console.error('Error:', err.message);
86 | break;
87 | }
88 | } else {
89 | console.error('Unknown error:', err);
90 | }
91 | }
92 | }
93 |
94 | verifyEmail();
95 | ```
96 |
97 | ### Example with Promises (then/catch)
98 |
99 | ```ts
100 | import NeverBounce from 'neverbounce';
101 |
102 | // Initialize NeverBounce client
103 | const client = new NeverBounce({apiKey: 'secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'});
104 |
105 | // Verify an email with Promise syntax
106 | client.single.check('support@neverbounce.com', true, true)
107 | .then(result => {
108 | console.log('Result: ' + result.getResult()); // prints: "valid"
109 | console.log('Result (numeric): ' + result.getNumericResult());
110 | console.log('Is Valid? ' + result.is(NeverBounce.result.valid));
111 |
112 | // Access the response data with proper typing
113 | const response = result.getResponse();
114 |
115 | if (response.address_info) {
116 | console.log('Host: ' + response.address_info.host);
117 | }
118 | })
119 | .catch(err => {
120 | // Handle errors with type checking
121 | if (err instanceof Error) {
122 | switch(err.type) {
123 | case NeverBounce.errors.AuthError:
124 | console.error('Auth Error:', err.message);
125 | break;
126 | case NeverBounce.errors.BadReferrerError:
127 | console.error('Bad Referrer Error:', err.message);
128 | break;
129 | case NeverBounce.errors.ThrottleError:
130 | console.error('Throttle Error:', err.message);
131 | break;
132 | case NeverBounce.errors.GeneralError:
133 | console.error('General Error:', err.message);
134 | break;
135 | default:
136 | console.error('Error:', err.message);
137 | break;
138 | }
139 | } else {
140 | console.error('Unknown error:', err);
141 | }
142 | });
143 | ```
144 |
145 | For more information you can check out the `/examples` directory contained within the repository or visit our official documentation [here](https://developers.neverbounce.com/v4.2/reference).
146 |
147 | Constants
148 | ---
149 |
150 | The library exposes several constants that make working with jobs, verification results and errors easier. They can be accessed from the root `NeverBounce` object via the `result`, `job`, and `errors` properties.
151 |
152 | ## Running Examples
153 |
154 | There are several examples contained within the `/examples` directory included in this repo. The examples are available in both JavaScript and TypeScript.
155 |
156 | ### Setting Up Environment Variables
157 |
158 | The examples use environment variables for configuration. Create a `.env` file in the project root with the following content (substituting in your own API key):
159 |
160 | ```
161 | NEVERBOUNCE_API_KEY=secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
162 | ```
163 |
164 | ### Building and Running as JavaScript
165 |
166 | Since the library is written in TypeScript, you'll need to compile it first to run the examples as JavaScript:
167 |
168 | ```bash
169 | # Build the project
170 | npm run build
171 |
172 | # Run an example (after compilation)
173 | node dist/examples/account-info.js
174 | ```
175 |
176 | ### Running TypeScript Examples
177 |
178 | To run the TypeScript examples, use the `example` script with the path to the TypeScript example:
179 |
180 | ```bash
181 | npm run example examples/single-check.ts
182 | ```
183 |
184 | This will execute the TypeScript file directly using ts-node without requiring a separate compilation step.
185 |
--------------------------------------------------------------------------------
/examples/account-info.ts:
--------------------------------------------------------------------------------
1 | import NeverBounce from '../src/NeverBounce.js';
2 | import dotenv from 'dotenv';
3 | import { AccountInfoResponse } from '../src/types.js';
4 |
5 | // Load environment variables from .local.env file
6 | dotenv.config();
7 |
8 | // Initialize NeverBounce client with API key from environment variables
9 | const client = new NeverBounce({
10 | apiKey: process.env.NEVERBOUNCE_API_KEY
11 | });
12 |
13 | // Get account info
14 | client.account.info()
15 | .then((resp: AccountInfoResponse) => console.log('[THEN]', resp))
16 | .catch((err: Error) => console.log('[THEN] ERROR: ' + err.message));
17 |
18 | // Alternative using async/await
19 | async function getAccountInfo(): Promise {
20 | try {
21 | const info: AccountInfoResponse = await client.account.info();
22 | console.log('[ASYNC] Account Info:', info);
23 | } catch (err) {
24 | if (err instanceof Error) {
25 | console.error('[ASYNC] Error:', err.message);
26 | } else {
27 | console.error('[ASYNC] Unknown error occurred');
28 | }
29 | }
30 | }
31 |
32 | getAccountInfo();
33 |
--------------------------------------------------------------------------------
/examples/error-handling.ts:
--------------------------------------------------------------------------------
1 | import NeverBounce from '../src/NeverBounce.js';
2 | import _Error from '../src/Errors.js';
3 | import VerificationObject from '../src/VerificationObject.js';
4 |
5 |
6 | // Initialize NeverBounce client
7 | // api key has been intentionally left out to trigger an error
8 | const client = new NeverBounce({});
9 |
10 | // Verify an email
11 | client.single.check('mike@neverbounce.com', true, true)
12 | .then((result: VerificationObject) => console.log('[THEN]', result))
13 | .catch((err: any) => {
14 | switch(err.type) {
15 | case NeverBounce.errors.AuthError:
16 | // The API credentials used are bad, have you reset them recently?
17 | console.log('[THEN]', err.message);
18 | break;
19 | case NeverBounce.errors.BadReferrerError:
20 | // The script is being used from an unauthorized source, you may need to
21 | // adjust your app's settings to allow it to be used from here
22 | console.log('[THEN]', err.message);
23 | break;
24 | case NeverBounce.errors.ThrottleError:
25 | // Too many requests in a short amount of time, try again shortly or adjust
26 | // your rate limit settings for this application in the dashboard
27 | console.log('[THEN]', err.message);
28 | break;
29 | case NeverBounce.errors.GeneralError:
30 | // A non recoverable API error occurred check the message for details
31 | console.log('[THEN]', err.message);
32 | break;
33 | default:
34 | // Other non specific errors
35 | console.log('[THEN]', err);
36 | break;
37 | }
38 | });
39 |
40 | // Alternative using async/await
41 | async function checkEmailWithErrorHandling(): Promise {
42 | try {
43 | // This will fail due to missing API key
44 | const result = await client.single.check('mike@neverbounce.com', true, true);
45 | console.log('[ASYNC]', result);
46 | } catch (err) {
47 | if (err instanceof _Error) {
48 | switch(err.type) {
49 | case NeverBounce.errors.AuthError:
50 | console.log('[ASYNC] Auth Error:', err.message);
51 | break;
52 | case NeverBounce.errors.BadReferrerError:
53 | console.log('[ASYNC] Bad Referrer Error:', err.message);
54 | break;
55 | case NeverBounce.errors.ThrottleError:
56 | console.log('[ASYNC] Throttle Error:', err.message);
57 | break;
58 | case NeverBounce.errors.GeneralError:
59 | console.log('[ASYNC] General Error:', err.message);
60 | break;
61 | default:
62 | console.log('[ASYNC] Unknown Error Type:', err.message);
63 | break;
64 | }
65 | } else if (err instanceof Error) {
66 | console.log('[ASYNC] Standard Error:', err.message);
67 | } else {
68 | console.log('[ASYNC] Unknown Error:', err);
69 | }
70 | }
71 | }
72 |
73 | // Run the async version
74 | checkEmailWithErrorHandling();
75 |
--------------------------------------------------------------------------------
/examples/jobs-create.ts:
--------------------------------------------------------------------------------
1 | import NeverBounce from '../src/NeverBounce.js';
2 | import { JobCreationResponse } from '../src/types.js';
3 | import dotenv from 'dotenv';
4 |
5 | // Load environment variables from .local.env file
6 | dotenv.config();
7 |
8 | // Initialize NeverBounce client with API key from environment variables
9 | const client = new NeverBounce({
10 | apiKey: process.env.NEVERBOUNCE_API_KEY
11 | });
12 |
13 | // Verify a list of emails
14 | client.jobs.create(
15 | [
16 | {
17 | id: '12345',
18 | email: 'support@neverbounce.com',
19 | name: 'Fred McValid'
20 | },
21 | {
22 | id: '12346',
23 | email: 'invalid@neverbounce.com',
24 | name: 'Bob McInvalid'
25 | }
26 | ],
27 | NeverBounce.job.inputType.supplied, // Either `supplied` or `remote_url`
28 | 'Created from Array.csv', // Friendly name that can be used to identify job
29 | false,
30 | true,
31 | )
32 | .then((resp: JobCreationResponse) => console.log('[THEN] Job ID: ' + resp.job_id))
33 | .catch((err: Error) => console.log('[THEN] ERROR: ' + err.message));
34 |
35 | // Alternative using async/await
36 | async function createJob(): Promise {
37 | try {
38 | const emails: any[] = [
39 | {
40 | id: '12345',
41 | email: 'support@neverbounce.com',
42 | name: 'Fred McValid'
43 | },
44 | {
45 | id: '12346',
46 | email: 'invalid@neverbounce.com',
47 | name: 'Bob McInvalid'
48 | }
49 | ];
50 | const result = await client.jobs.create(
51 | emails,
52 | NeverBounce.job.inputType.supplied,
53 | 'Created from Array.csv',
54 | false,
55 | true,
56 | );
57 |
58 | console.log('[ASYNC] Job created successfully!');
59 | console.log('[ASYNC] Job ID:', result.job_id);
60 | } catch (err) {
61 | if (err instanceof Error) {
62 | console.error('[ASYNC] Error creating job:', err.message);
63 | } else {
64 | console.error('[ASYNC] Unknown error occurred');
65 | }
66 | }
67 | }
68 |
69 | // Run the async version
70 | createJob();
71 |
--------------------------------------------------------------------------------
/examples/jobs-delete.ts:
--------------------------------------------------------------------------------
1 | import NeverBounce from '../src/NeverBounce.js';
2 | import { JobDeleteResponse } from '../src/types.js';
3 | import dotenv from 'dotenv';
4 |
5 | // Load environment variables from .local.env file
6 | dotenv.config();
7 |
8 | // Initialize NeverBounce client with API key from environment variables
9 | const client = new NeverBounce({
10 | apiKey: process.env.NEVERBOUNCE_API_KEY
11 | });
12 |
13 | // Delete a job
14 | client.jobs.delete(25999818)
15 | .then((resp: JobDeleteResponse) => console.log('[THEN]', resp))
16 | .catch((err: Error) => console.log('[THEN] ERROR: ' + err.message));
17 |
18 | // Alternative using async/await
19 | async function deleteJob(jobId: number): Promise {
20 | try {
21 | const result = await client.jobs.delete(jobId);
22 | console.log('[ASYNC] Job deleted successfully!');
23 | console.log('[ASYNC] status:', result.status);
24 | } catch (err) {
25 | if (err instanceof Error) {
26 | console.error('[ASYNC] Error deleting job:', err.message);
27 | } else {
28 | console.error('[ASYNC] Unknown error occurred');
29 | }
30 | }
31 | }
32 |
33 | // Run the async version with a specific job ID
34 | deleteJob(25999817);
35 |
--------------------------------------------------------------------------------
/examples/jobs-download.ts:
--------------------------------------------------------------------------------
1 | import NeverBounce from '../src/NeverBounce.js';
2 | import { JobDownloadRequest } from '../src/types.js';
3 | import dotenv from 'dotenv';
4 |
5 | // Load environment variables from .local.env file
6 | dotenv.config();
7 |
8 | // Initialize NeverBounce client with API key from environment variables
9 | const client = new NeverBounce({
10 | apiKey: process.env.NEVERBOUNCE_API_KEY
11 | });
12 |
13 | // Empty query object for download
14 | const query: Partial = {};
15 |
16 | // Download a job's results
17 | client.jobs.download(25999836, query)
18 | .then((resp: string) => console.log('[THEN]', resp))
19 | .catch((err: Error) => console.log('[THEN] ERROR: ' + err.message));
20 |
21 | // Alternative using async/await
22 | async function downloadJobResults(jobId: number): Promise {
23 | try {
24 | const csvData = await client.jobs.download(jobId);
25 | console.log('[ASYNC] Job results downloaded successfully!');
26 | console.log('[ASYNC] CSV Data (first 100 chars):', csvData.substring(0, 100) + '...');
27 |
28 | // In a real application, you might want to save this to a file
29 | // For example:
30 | // import fs from 'fs';
31 | // fs.writeFileSync(`job-${jobId}-results.csv`, csvData);
32 | } catch (err) {
33 | if (err instanceof Error) {
34 | console.error('[ASYNC] Error downloading job results:', err.message);
35 | } else {
36 | console.error('[ASYNC] Unknown error occurred');
37 | }
38 | }
39 | }
40 |
41 | // Run the async version with a specific job ID
42 | downloadJobResults(25999835);
43 |
--------------------------------------------------------------------------------
/examples/jobs-parse.ts:
--------------------------------------------------------------------------------
1 | import NeverBounce from '../src/NeverBounce.js';
2 | import { ApiResponse } from '../src/types.js';
3 | import dotenv from 'dotenv';
4 |
5 | // Load environment variables from .local.env file
6 | dotenv.config();
7 |
8 | // Initialize NeverBounce client with API key from environment variables
9 | const client = new NeverBounce({
10 | apiKey: process.env.NEVERBOUNCE_API_KEY
11 | });
12 |
13 | // Parse a job
14 | client.jobs.parse(25999824)
15 | .then((resp: ApiResponse) => console.log('[THEN]', resp))
16 | .catch((err: Error) => console.log('[THEN] ERROR: ' + err.message));
17 |
18 | // Alternative using async/await
19 | async function parseJob(jobId: number): Promise {
20 | try {
21 | const result = await client.jobs.parse(jobId);
22 | console.log('[ASYNC] Job parsing initiated successfully!');
23 | console.log('[ASYNC] Response:', result);
24 | } catch (err) {
25 | if (err instanceof Error) {
26 | console.error('[ASYNC] Error parsing job:', err.message);
27 | } else {
28 | console.error('[ASYNC] Unknown error occurred');
29 | }
30 | }
31 | }
32 |
33 | // Run the async version with a specific job ID
34 | parseJob(25999825);
35 |
--------------------------------------------------------------------------------
/examples/jobs-results.ts:
--------------------------------------------------------------------------------
1 | import NeverBounce from '../src/NeverBounce.js';
2 | import { JobResultsRequest, JobResultsResponse } from '../src/types.js';
3 | import dotenv from 'dotenv';
4 |
5 | // Load environment variables from .local.env file
6 | dotenv.config();
7 |
8 | // Initialize NeverBounce client with API key from environment variables
9 | const client = new NeverBounce({
10 | apiKey: process.env.NEVERBOUNCE_API_KEY
11 | });
12 |
13 | // Query parameters for job results
14 | const query: Partial = {
15 | // page: 1, // Page to start from
16 | // per_page: 10, // Number of items per page
17 | };
18 |
19 | // Get job results
20 | client.jobs.results(25999836, query)
21 | .then((resp: JobResultsResponse) => {
22 | console.log('[THEN] Results Count: ' + resp.results.length);
23 | console.log('[THEN] Results: ' + resp.results.map(result =>
24 | result.data.email + `(${result.verification.result})`
25 | ));
26 | })
27 | .catch((err: Error) => console.log('[THEN] ERROR: ' + err.message));
28 |
29 | // Alternative using async/await
30 | async function getJobResults(jobId: number, options: Partial = {}): Promise {
31 | try {
32 | const response = await client.jobs.results(jobId, options);
33 |
34 | console.log('[ASYNC] Job results retrieved successfully!');
35 | console.log(`[ASYNC] Total results: ${response.total_results}`);
36 | console.log(`[ASYNC] Total pages: ${response.total_pages}`);
37 | console.log(`[ASYNC] Current page: ${response.query.page}`);
38 |
39 | // Display the first few results
40 | if (response.results.length > 0) {
41 | console.log('\n[ASYNC] Sample results:');
42 | response.results.slice(0, 5).forEach((result, index) => {
43 | console.log(`[ASYNC] ${index + 1}. ${result.data.email} - Result: ${result.verification.result}`);
44 | });
45 | } else {
46 | console.log('[ASYNC] No results found.');
47 | }
48 | } catch (err) {
49 | if (err instanceof Error) {
50 | console.error('[ASYNC] Error retrieving job results:', err.message);
51 | } else {
52 | console.error('[ASYNC] Unknown error occurred');
53 | }
54 | }
55 | }
56 |
57 | // Run the async version with a specific job ID
58 | getJobResults(25999836, { page: 1, per_page: 10 });
59 |
--------------------------------------------------------------------------------
/examples/jobs-search.ts:
--------------------------------------------------------------------------------
1 | import NeverBounce from '../src/NeverBounce.js';
2 | import { JobSearchRequest, JobSearchResponse } from '../src/types.js';
3 | import dotenv from 'dotenv';
4 |
5 | // Load environment variables from .local.env file
6 | dotenv.config();
7 |
8 | // Initialize NeverBounce client with API key from environment variables
9 | const client = new NeverBounce({
10 | apiKey: process.env.NEVERBOUNCE_API_KEY
11 | });
12 |
13 | // Search parameters
14 | const searchParams: JobSearchRequest = {
15 | // job_id: 10000, // Filter jobs based on id
16 | // page: 1, // Page to start from
17 | // per_page: 10, // Number of items per page
18 | };
19 |
20 | // Search jobs
21 | client.jobs.search(searchParams)
22 | .then((resp: JobSearchResponse) => {
23 | console.log('[THEN] Jobs Returned: ' + resp.results.length);
24 | console.log('[THEN] Job IDs: ' + resp.results.map(job => job.id));
25 | })
26 | .catch((err: Error) => console.log('[THEN] ERROR: ' + err.message));
27 |
28 | // Alternative using async/await
29 | async function searchJobs(params: JobSearchRequest = {}): Promise {
30 | try {
31 | const response = await client.jobs.search(params);
32 |
33 | console.log('[ASYNC] Jobs search completed successfully!');
34 | console.log(`[ASYNC] Total jobs found: ${response.total_results}`);
35 | console.log(`[ASYNC] Total pages: ${response.total_pages}`);
36 | console.log(`[ASYNC] Current page: ${response.query.page}`);
37 |
38 | // Display the jobs
39 | if (response.results.length > 0) {
40 | console.log('\n[ASYNC] Jobs:');
41 | response.results.forEach((job, index) => {
42 | console.log(`[ASYNC] ${index + 1}. ID: ${job.id}, Filename: ${job.filename}, Status: ${job.job_status}`);
43 | console.log(`[ASYNC] Created: ${job.created_at}, Total: ${job.total}, Processed: ${job.total.processed}`);
44 | console.log(`[ASYNC] Valid: ${job.total.valid}, Invalid: ${job.total.invalid}, Catchall: ${job.total.catchall}, Disposable: ${job.total.disposable}, Unknown: ${job.total.unknown}`);
45 | console.log('[ASYNC] ---');
46 | });
47 | } else {
48 | console.log('[ASYNC] No jobs found.');
49 | }
50 | } catch (err) {
51 | if (err instanceof Error) {
52 | console.error('[ASYNC] Error searching jobs:', err.message);
53 | } else {
54 | console.error('[ASYNC] Unknown error occurred');
55 | }
56 | }
57 | }
58 |
59 | // Run the async version with specific search parameters
60 | searchJobs({ page: 1, per_page: 5 });
61 |
--------------------------------------------------------------------------------
/examples/jobs-start.ts:
--------------------------------------------------------------------------------
1 | import NeverBounce from '../src/NeverBounce.js';
2 | import { ApiResponse } from '../src/types.js';
3 | import dotenv from 'dotenv';
4 |
5 | // Load environment variables from .local.env file
6 | dotenv.config();
7 |
8 | // Initialize NeverBounce client with API key from environment variables
9 | const client = new NeverBounce({
10 | apiKey: process.env.NEVERBOUNCE_API_KEY
11 | });
12 |
13 | // Start job
14 | client.jobs.start(25999825)
15 | .then((resp: ApiResponse) => console.log('[THEN]', resp))
16 | .catch((err: Error) => console.log('[THEN] ERROR: ' + err.message));
17 |
18 | // Alternative using async/await
19 | async function startJob(jobId: number, runSample?: boolean, allowManualReview?: boolean): Promise {
20 | try {
21 | const result = await client.jobs.start(jobId, runSample, allowManualReview);
22 | console.log('[ASYNC] Job started successfully!');
23 | console.log('[ASYNC] Response:', result);
24 | } catch (err) {
25 | if (err instanceof Error) {
26 | console.error('[ASYNC] Error starting job:', err.message);
27 | } else {
28 | console.error('[ASYNC] Unknown error occurred');
29 | }
30 | }
31 | }
32 |
33 | // Run the async version with a specific job ID
34 | startJob(25999824);
35 |
36 |
--------------------------------------------------------------------------------
/examples/jobs-status.ts:
--------------------------------------------------------------------------------
1 | import NeverBounce from '../src/NeverBounce.js';
2 | import { JobStatusResponse } from '../src/types.js';
3 | import dotenv from 'dotenv';
4 |
5 | // Load environment variables from .local.env file
6 | dotenv.config();
7 |
8 | // Initialize NeverBounce client with API key from environment variables
9 | const client = new NeverBounce({
10 | apiKey: process.env.NEVERBOUNCE_API_KEY
11 | });
12 |
13 | // Get a job's status
14 | client.jobs.status(25999836)
15 | .then((resp: JobStatusResponse) => console.log('[THEN]', resp))
16 | .catch((err: Error) => console.log('[THEN] ERROR: ' + err.message));
17 |
18 | // Alternative using async/await
19 | async function checkJobStatus(jobId: number): Promise {
20 | try {
21 | const status = await client.jobs.status(jobId);
22 |
23 | console.log('[ASYNC] Job status retrieved successfully!');
24 | console.log(`[ASYNC] Job ID: ${status.id}`);
25 | console.log(`[ASYNC] Filename: ${status.filename}`);
26 | console.log(`[ASYNC] Status: ${status.job_status}`);
27 | console.log(`[ASYNC] Created: ${status.created_at}`);
28 | console.log(`[ASYNC] Started: ${status.started_at || 'Not started'}`);
29 | console.log(`[ASYNC] Finished: ${status.finished_at || 'Not finished'}`);
30 | console.log(`[ASYNC] Progress: ${status.percent_complete}%`);
31 |
32 | console.log('\n[ASYNC] Counts:');
33 | console.log(`[ASYNC] Total: ${status.total}`);
34 | console.log(`[ASYNC] Processed: ${status.total.processed}`);
35 | console.log(`[ASYNC] Valid: ${status.total.valid}`);
36 | console.log(`[ASYNC] Invalid: ${status.total.invalid}`);
37 | console.log(`[ASYNC] Catchall: ${status.total.catchall}`);
38 | console.log(`[ASYNC] Disposable: ${status.total.disposable}`);
39 | console.log(`[ASYNC] Unknown: ${status.total.unknown}`);
40 | console.log(`[ASYNC] Duplicates: ${status.total.duplicates}`);
41 |
42 | if (status.failure_reason) {
43 | console.log(`\n[ASYNC] Failure reason: ${status.failure_reason}`);
44 | }
45 | } catch (err) {
46 | if (err instanceof Error) {
47 | console.error('[ASYNC] Error checking job status:', err.message);
48 | } else {
49 | console.error('[ASYNC] Unknown error occurred');
50 | }
51 | }
52 | }
53 |
54 | // Run the async version with a specific job ID
55 | checkJobStatus(25999836);
56 |
--------------------------------------------------------------------------------
/examples/poe-confirm.ts:
--------------------------------------------------------------------------------
1 | import NeverBounce from '../src/NeverBounce.js';
2 | import { POEConfirmationResponse } from '../src/types.js';
3 | import dotenv from 'dotenv';
4 |
5 | // Load environment variables from .local.env file
6 | dotenv.config();
7 |
8 | // Initialize NeverBounce client with API key from environment variables
9 | const client = new NeverBounce({
10 | apiKey: process.env.NEVERBOUNCE_API_KEY
11 | });
12 |
13 | // Confirm POE (Proof of Email)
14 | client.poe.confirm(
15 | 'support@neverbounce.com',
16 | 0, // Valid result code (0 = valid)
17 | 'e3173fdbbdce6bad26522dae792911f2', // Confirmation token
18 | 'NBPOE-TXN-5942940c09669' // Transaction ID
19 | )
20 | .then((resp: POEConfirmationResponse) => console.log('[THEN]', resp))
21 | .catch((err: Error) => console.log('[THEN] ERROR: ' + err.message));
22 |
23 | // Alternative using async/await
24 | async function confirmPOE(
25 | email: string,
26 | result: number,
27 | confirmationToken: string,
28 | transactionId: string
29 | ): Promise {
30 | try {
31 | const response = await client.poe.confirm(email, result, confirmationToken, transactionId);
32 |
33 | console.log('[ASYNC] POE confirmation successful!');
34 | console.log(`[ASYNC] Token confirmed: ${response.token_confirmed}`);
35 | } catch (err) {
36 | if (err instanceof Error) {
37 | console.error('[ASYNC] Error confirming POE:', err.message);
38 | } else {
39 | console.error('[ASYNC] Unknown error occurred');
40 | }
41 | }
42 | }
43 |
44 | // Run the async version with specific parameters
45 | confirmPOE(
46 | 'support@neverbounce.com',
47 | 0, // Valid result code (0 = valid)
48 | 'e3173fdbbdce6bad26522dae792911f2', // Confirmation token
49 | 'NBPOE-TXN-5942940c09669' // Transaction ID
50 | );
51 |
--------------------------------------------------------------------------------
/examples/single-check.ts:
--------------------------------------------------------------------------------
1 | import NeverBounce from '../src/NeverBounce.js';
2 | import dotenv from 'dotenv';
3 |
4 | // Load environment variables from .local.env file
5 | dotenv.config();
6 |
7 | // Example of using Promise-based approach with the NeverBounce API
8 | const client = new NeverBounce({
9 | apiKey: process.env.NEVERBOUNCE_API_KEY
10 | });
11 |
12 | // Verify an email with address_info and credits_info
13 | client.single.check('support@neverbounce.com', true, true)
14 | .then(result => {
15 | console.log('[THEN] Result: ' + result.getResult());
16 | console.log('[THEN] Result (numeric): ' + result.getNumericResult());
17 | console.log('[THEN] Is Valid? ' + result.is(NeverBounce.result.valid));
18 |
19 | // Access the response data with proper typing
20 | const response = result.getResponse();
21 |
22 | if (response.credits_info) {
23 | console.log('[THEN] Free Credits Used: ' + response.credits_info.free_credits_used);
24 | console.log('[THEN] Paid Credits Used: ' + response.credits_info.paid_credits_used);
25 | }
26 |
27 | if (response.address_info) {
28 | console.log('[THEN] Host: ' + response.address_info.host);
29 | }
30 | })
31 | .catch(err => {
32 | if (err instanceof Error) {
33 | console.log('[THEN] ERROR: ' + err.message);
34 | } else {
35 | console.log('[THEN] Unknown error occurred');
36 | }
37 | });
38 |
39 | // Example of using async/await with the NeverBounce API
40 | const runExample = async (): Promise => {
41 | // Initialize NeverBounce client with API key from environment variables
42 | const client = new NeverBounce({
43 | apiKey: process.env.NEVERBOUNCE_API_KEY
44 | });
45 |
46 | try {
47 | // Verify an email with address_info and credits_info
48 | const result = await client.single.check('support@neverbounce.com', true, true);
49 |
50 | console.log('[ASYNC] Result: ' + result.getResult());
51 | console.log('[ASYNC] Result (numeric): ' + result.getNumericResult());
52 | console.log('[ASYNC] Is Valid? ' + result.is(NeverBounce.result.valid));
53 |
54 | // Access the response data with proper typing
55 | const response = result.getResponse();
56 |
57 | if (response.credits_info) {
58 | console.log('[ASYNC] Free Credits Used: ' + response.credits_info.free_credits_used);
59 | console.log('[ASYNC] Paid Credits Used: ' + response.credits_info.paid_credits_used);
60 | }
61 |
62 | if (response.address_info) {
63 | console.log('[ASYNC] Host: ' + response.address_info.host);
64 | }
65 | } catch (err) {
66 | // Type guard to check if it's our custom error type
67 | if (err instanceof Error) {
68 | console.log('[ASYNC] ERROR: ' + err.message);
69 | } else {
70 | console.log('[ASYNC] Unknown error occurred');
71 | }
72 | }
73 | };
74 |
75 | // Run the example
76 | runExample().catch(err => {
77 | console.error('[ASYNC] Unhandled error:', err);
78 | });
79 |
--------------------------------------------------------------------------------
/jest.config.cjs:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest').JestConfigWithTsJest} */
2 | module.exports = {
3 | preset: 'ts-jest',
4 | testEnvironment: 'node',
5 | extensionsToTreatAsEsm: ['.ts'],
6 | moduleNameMapper: {
7 | '^(\\.{1,2}/.*)\\.js$': '$1',
8 | },
9 | transform: {
10 | '^.+\\.tsx?$': [
11 | 'ts-jest',
12 | {
13 | useESM: true,
14 | },
15 | ],
16 | },
17 | testMatch: ['**/tests/**/*.test.ts'],
18 | collectCoverage: true,
19 | coverageDirectory: 'coverage',
20 | collectCoverageFrom: [
21 | 'src/**/*.ts',
22 | '!src/types.ts',
23 | '!src/index.ts',
24 | ],
25 | setupFilesAfterEnv: ['./tests/setup.ts'],
26 | };
27 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "neverbounce",
3 | "version": "5.0.5",
4 | "description": "An API wrapper for the NeverBounce API",
5 | "engines": {
6 | "node": ">=18.0.0"
7 | },
8 | "type": "module",
9 | "main": "dist/cjs/src/index.js",
10 | "module": "dist/esm/src/index.js",
11 | "types": "dist/esm/src/index.d.ts",
12 | "exports": {
13 | ".": {
14 | "import": "./dist/esm/src/index.js",
15 | "require": "./dist/cjs/src/index.js",
16 | "types": "./dist/esm/src/index.d.ts"
17 | }
18 | },
19 | "files": [
20 | "dist/esm",
21 | "dist/cjs"
22 | ],
23 | "repository": {
24 | "type": "git",
25 | "url": "https://github.com/NeverBounce/NeverBounceApi-NodeJS"
26 | },
27 | "scripts": {
28 | "build": "npm run build:esm && npm run build:cjs && npm run build:package",
29 | "build:esm": "tsc -p tsconfig.json",
30 | "build:cjs": "tsc -p tsconfig.cjs.json",
31 | "build:package": "mkdir -p dist/cjs && echo '{\"type\": \"commonjs\"}' > dist/cjs/package.json && cp 'package.json' 'README.md' dist/",
32 | "lint": "eslint src/**/*.ts",
33 | "lint:fix": "eslint src/**/*.ts --fix",
34 | "prepublishOnly": "npm run build",
35 | "example": "tsx",
36 | "test": "NODE_OPTIONS=--experimental-vm-modules npx jest --config=jest.config.cjs",
37 | "test:watch": "jest --watch"
38 | },
39 | "keywords": [
40 | "email",
41 | "email verification",
42 | "email validation",
43 | "email cleaning",
44 | "verification",
45 | "validation",
46 | "cleaning"
47 | ],
48 | "author": "NeverBounce (https://neverbounce.com)",
49 | "contributors": [
50 | "Mike Mollick ",
51 | "Omri Katz "
52 | ],
53 | "homepage": "https://neverbounce.com",
54 | "license": "MIT",
55 | "devDependencies": {
56 | "@types/jest": "^29.5.14",
57 | "@types/node": "^20.17.41",
58 | "@typescript-eslint/eslint-plugin": "^7.0.0",
59 | "@typescript-eslint/parser": "^7.0.0",
60 | "chai": "^4.3.10",
61 | "eslint": "^8.56.0",
62 | "jest": "29.7.0",
63 | "nock": "^13.4.0",
64 | "ts-jest": "29.1.1",
65 | "ts-node": "^10.9.2",
66 | "typescript": "^5.4.5"
67 | },
68 | "dependencies": {
69 | "dotenv": "^16.5.0"
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/Account.ts:
--------------------------------------------------------------------------------
1 | import HttpsClient from './HttpsClient.js';
2 | import { AccountInfoResponse } from './types.js';
3 |
4 | /**
5 | * Account API endpoints
6 | */
7 | class Account extends HttpsClient {
8 | /**
9 | * Returns account info
10 | * @returns Promise with account information
11 | */
12 | async info(): Promise {
13 | return this.request({
14 | method: 'GET',
15 | path: 'account/info'
16 | });
17 | }
18 | }
19 |
20 | export default Account;
21 |
--------------------------------------------------------------------------------
/src/Errors.ts:
--------------------------------------------------------------------------------
1 | // Add Node.js specific Error interface extension
2 | declare global {
3 | interface ErrorConstructor {
4 | captureStackTrace?(targetObject: object, constructorOpt?: new (...args: any[]) => any): void;
5 | }
6 | }
7 |
8 | /**
9 | * NeverBounce API Error class
10 | */
11 | class _Error extends Error {
12 | public name: string;
13 | public type: string;
14 | public message: string;
15 |
16 | /**
17 | * Error constructor
18 | * @param type Error type
19 | * @param message Error message
20 | */
21 | constructor(type?: string, message?: string) {
22 | super(message || 'General Error');
23 | this.name = 'NeverBounce Error';
24 | this.type = type || 'GeneralError';
25 | this.message = message || 'General Error';
26 |
27 | // Maintains proper stack trace for where our error was thrown (only available on V8)
28 | if (Error.captureStackTrace) {
29 | Error.captureStackTrace(this, _Error);
30 | }
31 | }
32 |
33 | /**
34 | * Error type constants
35 | */
36 | static readonly AuthError: string = 'AuthError';
37 | static readonly BadReferrerError: string = 'BadReferrerError';
38 | static readonly GeneralError: string = 'GeneralError'; // Fallback error
39 | static readonly ThrottleError: string = 'ThrottleError';
40 |
41 | /**
42 | * Error lookup table
43 | */
44 | static readonly _lut: Record = {
45 | 'general_failure': _Error.GeneralError, // Fallback error
46 | 'auth_failure': _Error.AuthError,
47 | 'bad_referrer': _Error.BadReferrerError,
48 | 'throttle_triggered': _Error.ThrottleError,
49 | };
50 | }
51 |
52 | export default _Error;
53 |
--------------------------------------------------------------------------------
/src/HttpsClient.ts:
--------------------------------------------------------------------------------
1 | import _Error from './Errors.js';
2 | import { ApiResponse, RequestOptions } from './types.js';
3 |
4 | /**
5 | * Interface for NeverBounce client
6 | */
7 | interface NeverBounceClient {
8 | getConfig(): {
9 | apiKey: string | null;
10 | apiVersion: string;
11 | timeout: number;
12 | opts: RequestOptions;
13 | };
14 | getRequestOpts(params: Partial): RequestOptions;
15 | }
16 |
17 | /**
18 | * HTTP Client for making API requests using fetch
19 | */
20 | class HttpsClient {
21 | private _nb: NeverBounceClient;
22 | private _version: string;
23 |
24 | /**
25 | * Constructor
26 | * @param _nb NeverBounce client instance
27 | */
28 | constructor(_nb: NeverBounceClient) {
29 | this._nb = _nb;
30 | // Get version from package.json
31 | this._version = '5.0.0'; // This will be replaced with dynamic import when we implement the build process
32 | }
33 |
34 | /**
35 | * Performs API requests using fetch
36 | * @param params Request parameters
37 | * @param data Request data
38 | * @returns Promise with API response
39 | */
40 | async request(params: Partial, data: Record = {}): Promise {
41 | const config = this._nb.getConfig();
42 | // Set key
43 | data.key = config.apiKey;
44 |
45 | // Get request options
46 | const opts = this._nb.getRequestOpts(params);
47 | const path = opts.path ? `/${config.apiVersion}/${opts.path}` : '';
48 |
49 | // Build URL
50 | const url = new URL(`https://${opts.host}:${opts.port}${path}`);
51 |
52 | // Set up fetch options
53 | const fetchOptions: RequestInit = {
54 | method: 'POST',
55 | headers: {
56 | 'Content-Type': 'application/json',
57 | 'Accept': opts.acceptedType,
58 | 'User-Agent': `NeverBounceApi-NodeJS/${this._version}`
59 | },
60 | body: JSON.stringify(data)
61 | };
62 |
63 | // Set up AbortController for timeout
64 | const controller = new AbortController();
65 | if (config.timeout) {
66 | fetchOptions.signal = controller.signal;
67 | }
68 |
69 | try {
70 | const response = await fetch(url.toString(), fetchOptions);
71 |
72 | // Handle HTTP error codes
73 | if (response.status >= 400 && response.status < 500) {
74 | throw new _Error(
75 | _Error.GeneralError,
76 | `We were unable to complete your request. The following information was supplied: \n\n(Request error [status ${response.status}])`
77 | );
78 | }
79 |
80 | if (response.status >= 500) {
81 | throw new _Error(
82 | _Error.GeneralError,
83 | `We were unable to complete your request. The following information was supplied: \n\n(Internal error [status ${response.status}])`
84 | );
85 | }
86 |
87 | // Check content type
88 | const contentType = response.headers.get('content-type');
89 |
90 | if (contentType?.includes('application/json')) {
91 | const decoded = await response.json() as T;
92 |
93 | // Check for missing status and error messages
94 | if (decoded.status === undefined || (decoded.status !== 'success' && decoded.message === undefined)) {
95 | throw new _Error(
96 | _Error.GeneralError,
97 | 'The response from server is incomplete. Either a status code was not included or ' +
98 | 'an error was returned without an error message. Try the request again, if ' +
99 | 'this error persists let us know at support@neverbounce.com.' +
100 | `\n\n(Internal error [status ${response.status}])`
101 | );
102 | }
103 |
104 | // Handle error statuses
105 | if (decoded.status !== 'success') {
106 | const errorType = _Error._lut[decoded.status] || _Error.GeneralError;
107 |
108 | if (errorType === _Error.AuthError) {
109 | throw new _Error(
110 | _Error.AuthError,
111 | `We were unable to authenticate your request. The following information was supplied: ${decoded.message}\n\n(auth_failure)`
112 | );
113 | } else {
114 | throw new _Error(
115 | errorType,
116 | `We were unable to complete your request. The following information was supplied: ${decoded.message}\n\n(${decoded.status})`
117 | );
118 | }
119 | }
120 |
121 | return decoded;
122 | } else if (contentType && contentType !== opts.acceptedType) {
123 | throw new _Error(
124 | _Error.GeneralError,
125 | `The response from NeverBounce was returned with the type "${contentType}" but a response ` +
126 | `type of "${opts.acceptedType}" was expected. Try the request again, if this error persists ` +
127 | 'let us know at support@neverbounce.com.\n\n(Internal error)'
128 | );
129 | }
130 |
131 | // Return text response for non-JSON content types
132 | const textResponse = await response.text();
133 | return textResponse as unknown as T;
134 |
135 | } catch (error) {
136 | // Handle fetch errors or our custom errors
137 | if (error instanceof _Error) {
138 | throw error;
139 | }
140 |
141 | // Handle timeout or network errors
142 | if (error instanceof Error) {
143 | if (error.name === 'AbortError') {
144 | throw new _Error(_Error.GeneralError, 'The request timed out. Please try again later.');
145 | }
146 | throw new _Error(_Error.GeneralError, `Network error: ${error.message}`);
147 | }
148 |
149 | // Fallback for unknown errors
150 | throw new _Error(_Error.GeneralError, 'An unknown error occurred during the request.');
151 | }
152 | }
153 | }
154 |
155 | export default HttpsClient;
156 |
--------------------------------------------------------------------------------
/src/Jobs.ts:
--------------------------------------------------------------------------------
1 | import HttpsClient from './HttpsClient.js';
2 | import {
3 | ApiResponse,
4 | JobCreationRequest,
5 | JobCreationResponse,
6 | JobDeleteResponse,
7 | JobDownloadRequest,
8 | JobResultsRequest,
9 | JobResultsResponse,
10 | JobSearchRequest,
11 | JobSearchResponse,
12 | JobStatusResponse
13 | } from './types.js';
14 |
15 | /**
16 | * Jobs API endpoints
17 | */
18 | class Jobs extends HttpsClient {
19 | /**
20 | * Search for jobs
21 | * @param query Search query parameters
22 | * @returns Promise with search results
23 | */
24 | async search(query?: JobSearchRequest): Promise {
25 | return this.request({
26 | method: 'GET',
27 | path: 'jobs/search'
28 | }, query || {});
29 | }
30 |
31 | /**
32 | * Creates a job
33 | * @param input Input data (array of emails or remote URL)
34 | * @param inputlocation Input location type ('remote_url' or 'supplied')
35 | * @param filename Filename for the job
36 | * @param runsample Whether to run a sample
37 | * @param autoparse Whether to automatically parse
38 | * @param autostart Whether to automatically start
39 | * @param historicalData Whether to leverage historical data
40 | * @param allowManualReview Whether to allow manual review
41 | * @param callbackUrl URL to call when job completes
42 | * @param callbackHeaders Headers to include in callback request
43 | * @returns Promise with job creation response
44 | */
45 | async create(
46 | input: any[] | string,
47 | inputlocation?: string,
48 | filename?: string,
49 | runsample?: boolean,
50 | autoparse?: boolean,
51 | autostart?: boolean,
52 | historicalData?: boolean,
53 | allowManualReview?: boolean,
54 | callbackUrl?: string,
55 | callbackHeaders?: Record
56 | ): Promise {
57 | const data: JobCreationRequest & {
58 | request_meta_data?: { leverage_historical_data: number };
59 | allow_manual_review?: boolean | null;
60 | callback_url?: string | null;
61 | callback_headers?: Record | null;
62 | } = {
63 | input: Array.isArray(input) ? input : [input],
64 | input_location: inputlocation,
65 | filename,
66 | run_sample: runsample || null,
67 | auto_start: autostart || null,
68 | auto_parse: autoparse || null,
69 | allow_manual_review: allowManualReview || null,
70 | callback_url: callbackUrl || null,
71 | callback_headers: callbackHeaders || null
72 | };
73 |
74 | if (historicalData !== undefined) {
75 | data.request_meta_data = { leverage_historical_data: historicalData ? 1 : 0 };
76 | }
77 |
78 | return this.request({
79 | method: 'POST',
80 | path: 'jobs/create'
81 | }, data);
82 | }
83 |
84 | /**
85 | * Starts parsing job after creation
86 | * @param jobid Job ID
87 | * @param autostart Whether to automatically start
88 | * @returns Promise with parse response
89 | */
90 | async parse(jobid: number, autostart?: boolean): Promise {
91 | return this.request({
92 | method: 'POST',
93 | path: 'jobs/parse'
94 | }, {
95 | job_id: jobid,
96 | auto_start: autostart || undefined
97 | });
98 | }
99 |
100 | /**
101 | * Starts job waiting to be started
102 | * @param jobid Job ID
103 | * @param runsample Whether to run a sample
104 | * @param allowManualReview Whether to allow manual review
105 | * @returns Promise with start response
106 | */
107 | async start(jobid: number, runsample?: boolean, allowManualReview?: boolean): Promise {
108 | return this.request({
109 | method: 'POST',
110 | path: 'jobs/start'
111 | }, {
112 | job_id: jobid,
113 | run_sample: runsample || undefined,
114 | allow_manual_review: allowManualReview || undefined
115 | });
116 | }
117 |
118 | /**
119 | * Gets job status
120 | * @param jobid Job ID
121 | * @returns Promise with job status
122 | */
123 | async status(jobid: number): Promise {
124 | return this.request({
125 | method: 'GET',
126 | path: 'jobs/status'
127 | }, {
128 | job_id: jobid
129 | });
130 | }
131 |
132 | /**
133 | * Retrieves job results
134 | * @param jobid Job ID
135 | * @param query Additional query parameters
136 | * @returns Promise with job results
137 | */
138 | async results(jobid: number, query?: Omit): Promise {
139 | return this.request({
140 | method: 'GET',
141 | path: 'jobs/results'
142 | }, Object.assign({ job_id: jobid }, query || {}));
143 | }
144 |
145 | /**
146 | * Downloads results as CSV
147 | * @param jobid Job ID
148 | * @param query Additional query parameters
149 | * @returns Promise with CSV data
150 | */
151 | async download(jobid: number, query?: Omit): Promise {
152 | return this.request({
153 | acceptedType: 'application/octet-stream',
154 | method: 'GET',
155 | path: 'jobs/download'
156 | }, Object.assign({ job_id: jobid }, query || {}));
157 | }
158 |
159 | /**
160 | * Deletes a job
161 | * @param jobid Job ID
162 | * @returns Promise with delete response
163 | */
164 | async delete(jobid: number): Promise {
165 | return this.request({
166 | method: 'POST',
167 | path: 'jobs/delete'
168 | }, {
169 | job_id: jobid
170 | });
171 | }
172 |
173 | /**
174 | * Job input type constants
175 | */
176 | static readonly remote: string = 'remote_url';
177 | static readonly supplied: string = 'supplied';
178 |
179 | /**
180 | * Helper object for job types and statuses
181 | * @since 4.1.4
182 | */
183 | static readonly helpers = {
184 | inputType: {
185 | remote: Jobs.remote,
186 | supplied: Jobs.supplied
187 | },
188 | status: {
189 | under_review: 'under_review',
190 | queued: 'queued',
191 | failed: 'failed',
192 | complete: 'complete',
193 | running: 'running',
194 | parsing: 'parsing',
195 | waiting: 'waiting',
196 | waiting_analyzed: 'waiting_analyzed',
197 | uploading: 'uploading'
198 | }
199 | };
200 | }
201 |
202 | export default Jobs;
203 |
--------------------------------------------------------------------------------
/src/NeverBounce.ts:
--------------------------------------------------------------------------------
1 | import { NeverBounceConfig, RequestOptions } from './types.js';
2 |
3 | // Import resource classes
4 | import Account from './Account.js';
5 | import Jobs from './Jobs.js';
6 | import POE from './POE.js';
7 | import Single from './Single.js';
8 | import VerificationObject from './VerificationObject.js';
9 | import _Error from './Errors.js';
10 |
11 | /**
12 | * Main NeverBounce API client class
13 | */
14 | class NeverBounce {
15 | /**
16 | * Resource instances
17 | */
18 | public account: Account;
19 | public jobs: Jobs;
20 | public poe: POE;
21 | public single: Single;
22 |
23 | /**
24 | * Default config values
25 | */
26 | static readonly defaultConfig: NeverBounceConfig = {
27 | apiVersion: 'v4.2',
28 | apiKey: null,
29 | timeout: 30000,
30 | opts: {
31 | acceptedType: 'application/json',
32 | host: 'api.neverbounce.com',
33 | port: 443,
34 | headers: {
35 | 'Content-Type': 'application/json',
36 | 'User-Agent': 'NeverBounce-Node/5.0.0'
37 | }
38 | }
39 | };
40 |
41 | /**
42 | * Verification result helpers
43 | */
44 | static readonly result = VerificationObject.helpers;
45 |
46 | /**
47 | * Job helpers
48 | */
49 | static readonly job = Jobs.helpers;
50 |
51 | /**
52 | * Error types
53 | */
54 | static readonly errors = _Error;
55 |
56 | /**
57 | * Configuration
58 | */
59 | private config: NeverBounceConfig;
60 |
61 | /**
62 | * Initializes NeverBounce object
63 | * @param config Configuration options
64 | */
65 | constructor(config: Partial = {}) {
66 | // Create config by merging defaults with user config
67 | this.config = {
68 | ...structuredClone(NeverBounce.defaultConfig),
69 | ...config,
70 | opts: {
71 | ...NeverBounce.defaultConfig.opts,
72 | ...(config.opts || {}),
73 | headers: {
74 | ...NeverBounce.defaultConfig.opts.headers,
75 | ...(config.opts?.headers || {})
76 | }
77 | }
78 | };
79 |
80 | // Initialize resources
81 | this.account = new Account(this);
82 | this.jobs = new Jobs(this);
83 | this.poe = new POE(this);
84 | this.single = new Single(this);
85 | }
86 |
87 | /**
88 | * Returns config
89 | */
90 | getConfig(): NeverBounceConfig {
91 | return this.config;
92 | }
93 |
94 | /**
95 | * Make request options
96 | */
97 | getRequestOpts(opts: Partial = {}): RequestOptions {
98 | return {
99 | ...this.config.opts,
100 | ...opts,
101 | headers: {
102 | ...this.config.opts.headers,
103 | ...(opts.headers || {})
104 | }
105 | };
106 | }
107 |
108 | /**
109 | * Update configuration
110 | */
111 | updateConfig(config: Partial): void {
112 | if (config.apiKey) this.config.apiKey = config.apiKey;
113 | if (config.apiVersion) this.config.apiVersion = config.apiVersion;
114 | if (config.timeout) this.config.timeout = config.timeout;
115 |
116 | if (config.opts) {
117 | this.config.opts = {
118 | ...this.config.opts,
119 | ...config.opts,
120 | headers: {
121 | ...this.config.opts.headers,
122 | ...(config.opts.headers || {})
123 | }
124 | };
125 | }
126 | }
127 |
128 | /**
129 | * Set api key (convenience method)
130 | * @param key API key
131 | */
132 | setApiKey(key: string): void {
133 | this.config.apiKey = key;
134 | }
135 |
136 | /**
137 | * Set api host (convenience method)
138 | * @param host API host
139 | */
140 | setHost(host: string): void {
141 | this.config.opts.host = host;
142 | }
143 | }
144 |
145 | export default NeverBounce;
146 |
--------------------------------------------------------------------------------
/src/POE.ts:
--------------------------------------------------------------------------------
1 | import HttpsClient from './HttpsClient.js';
2 | import { POEConfirmationRequest, POEConfirmationResponse } from './types.js';
3 |
4 | /**
5 | * POE (Proof of Email) API endpoints
6 | */
7 | class POE extends HttpsClient {
8 | /**
9 | * Confirms a POE token
10 | * @param email Email address
11 | * @param result Verification result
12 | * @param confirmationToken Confirmation token
13 | * @param transactionId Transaction ID
14 | * @returns Promise with confirmation response
15 | */
16 | async confirm(
17 | email: string,
18 | result: number,
19 | confirmationToken: string,
20 | transactionId: string
21 | ): Promise {
22 | const data: POEConfirmationRequest = {
23 | email,
24 | result,
25 | confirmation_token: confirmationToken,
26 | transaction_id: transactionId
27 | };
28 |
29 | return this.request({
30 | method: 'POST',
31 | path: 'poe/confirm'
32 | }, data);
33 | }
34 | }
35 |
36 | export default POE;
37 |
--------------------------------------------------------------------------------
/src/Single.ts:
--------------------------------------------------------------------------------
1 | import HttpsClient from './HttpsClient.js';
2 | import VerificationObject from './VerificationObject.js';
3 | import { SingleVerificationRequest, SingleVerificationResponse } from './types.js';
4 |
5 | /**
6 | * Single email verification API endpoints
7 | */
8 | class Single extends HttpsClient {
9 | /**
10 | * Performs the verification of a single email
11 | * @param email Email address to verify
12 | * @param address_info Whether to include address info in the response
13 | * @param credits_info Whether to include credits info in the response
14 | * @param timeout Timeout for the verification in milliseconds
15 | * @param historicalData Whether to leverage historical data
16 | * @returns Promise with verification object
17 | */
18 | async check(
19 | email: string,
20 | address_info?: boolean,
21 | credits_info?: boolean,
22 | timeout?: number,
23 | historicalData?: boolean
24 | ): Promise {
25 | const data: SingleVerificationRequest & { request_meta_data?: { leverage_historical_data: number } } = {
26 | email,
27 | address_info: address_info || undefined,
28 | credits_info: credits_info || undefined,
29 | timeout: timeout || undefined
30 | };
31 |
32 | if (historicalData !== undefined) {
33 | data.request_meta_data = { leverage_historical_data: historicalData ? 1 : 0 };
34 | }
35 |
36 | const response = await this.request({
37 | method: 'GET',
38 | path: 'single/check'
39 | }, data);
40 |
41 | return new VerificationObject(response);
42 | }
43 | }
44 |
45 | export default Single;
46 |
--------------------------------------------------------------------------------
/src/VerificationObject.ts:
--------------------------------------------------------------------------------
1 | import { SingleVerificationResponse, VerificationFlags } from './types.js';
2 |
3 | /**
4 | * Verification Object class for handling verification results
5 | */
6 | class VerificationObject {
7 | private response: SingleVerificationResponse;
8 |
9 | /**
10 | * Constructor
11 | * @param response Verification response from API
12 | */
13 | constructor(response: SingleVerificationResponse) {
14 | this.response = response;
15 | }
16 |
17 | /**
18 | * Get the full response object
19 | * @returns The verification response
20 | */
21 | getResponse(): SingleVerificationResponse {
22 | return this.response;
23 | }
24 |
25 | /**
26 | * Returns result
27 | * @returns The verification result
28 | */
29 | getResult(): string | number {
30 | return this.response.result;
31 | }
32 |
33 | /**
34 | * Returns text code
35 | * @returns The numeric result code
36 | */
37 | getNumericResult(): number | string {
38 | return (VerificationObject as any)[this.getResult()];
39 | }
40 |
41 | /**
42 | * Returns true if result is of specified types
43 | * @param codes Verification result codes to check
44 | * @returns Whether the result matches any of the specified codes
45 | */
46 | is(codes: string | number | Array): boolean {
47 | if (Array.isArray(codes)) {
48 | return (codes.indexOf(this.getResult() as any) > -1 || codes.indexOf(this.getNumericResult() as any) > -1);
49 | } else {
50 | return (codes === this.getResult() || codes === this.getNumericResult());
51 | }
52 | }
53 |
54 | /**
55 | * Returns true if result is NOT of specified types
56 | * @param codes Verification result codes to check
57 | * @returns Whether the result does not match any of the specified codes
58 | */
59 | not(codes: string | number | Array): boolean {
60 | if (Array.isArray(codes)) {
61 | return (codes.indexOf(this.getResult() as any) === -1 && codes.indexOf(this.getNumericResult() as any) === -1);
62 | } else {
63 | return (codes !== this.getResult() && codes !== this.getNumericResult());
64 | }
65 | }
66 |
67 | /**
68 | * Returns true if the flag was returned
69 | * @param flag Flag to check
70 | * @returns Whether the flag is present
71 | */
72 | hasFlag(flag: string): boolean {
73 | return this.response
74 | && this.response.flags
75 | && this.response.flags.indexOf(flag) > -1;
76 | }
77 |
78 | /**
79 | * Result code constants
80 | */
81 | static readonly valid: number = 0;
82 | static readonly invalid: number = 1;
83 | static readonly disposable: number = 2;
84 | static readonly catchall: number = 3;
85 | static readonly unknown: number = 4;
86 |
87 | /**
88 | * Helper object for result codes and flags
89 | * @since 4.1.4
90 | */
91 | static readonly helpers: {
92 | [key: string]: any;
93 | [key: number]: string;
94 | valid: number;
95 | invalid: number;
96 | disposable: number;
97 | catchall: number;
98 | unknown: number;
99 | flags: VerificationFlags;
100 | } = {
101 | // Numerically indexed
102 | [VerificationObject.valid]: 'valid',
103 | [VerificationObject.invalid]: 'invalid',
104 | [VerificationObject.disposable]: 'disposable',
105 | [VerificationObject.catchall]: 'catchall',
106 | [VerificationObject.unknown]: 'unknown',
107 |
108 | // Text indexed
109 | valid: VerificationObject.valid,
110 | invalid: VerificationObject.invalid,
111 | disposable: VerificationObject.disposable,
112 | catchall: VerificationObject.catchall,
113 | unknown: VerificationObject.unknown,
114 |
115 | flags: {
116 | has_dns: 'has_dns',
117 | has_dns_mx: 'has_dns_mx',
118 | bad_syntax: 'bad_syntax',
119 | free_email_host: 'free_email_host',
120 | profanity: 'profanity',
121 | role_account: 'role_account',
122 | disposable_email: 'disposable_email',
123 | government_host: 'government_host',
124 | academic_host: 'academic_host',
125 | military_host: 'military_host',
126 | international_host: 'international_host',
127 | squatter_host: 'squatter_host',
128 | spelling_mistake: 'spelling_mistake',
129 | bad_dns: 'bad_dns',
130 | temporary_dns_error: 'temporary_dns_error',
131 | connect_fails: 'connect_fails',
132 | accepts_all: 'accepts_all',
133 | contains_alias: 'contains_alias',
134 | contains_subdomain: 'contains_subdomain',
135 | smtp_connectable: 'smtp_connectable',
136 | spamtrap_network: 'spamtrap_network',
137 | }
138 | };
139 |
140 | /**
141 | * @deprecated 4.1.4 Will be removed in next minor release
142 | */
143 | static readonly numericCodes: Record = {
144 | valid: VerificationObject.valid,
145 | invalid: VerificationObject.invalid,
146 | disposable: VerificationObject.disposable,
147 | catchall: VerificationObject.catchall,
148 | unknown: VerificationObject.unknown,
149 | };
150 | }
151 |
152 | export default VerificationObject;
153 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * NeverBounce API Node.js Client
3 | * @module neverbounce
4 | */
5 |
6 | import NeverBounce from './NeverBounce.js';
7 |
8 | export default NeverBounce;
9 | export { default as VerificationObject } from './VerificationObject.js';
10 | export { default as _Error } from './Errors.js';
11 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Common type definitions for the NeverBounce API
3 | */
4 |
5 | /**
6 | * NeverBounce API Configuration
7 | */
8 | export interface NeverBounceConfig {
9 | apiVersion: string;
10 | apiKey: string | null;
11 | timeout: number;
12 | opts: RequestOptions;
13 | }
14 |
15 | /**
16 | * HTTP Request Options
17 | */
18 | export interface RequestOptions {
19 | acceptedType: string;
20 | host: string;
21 | port: number;
22 | headers: Record;
23 | path?: string;
24 | method?: string;
25 | }
26 |
27 | /**
28 | * API Response
29 | */
30 | export interface ApiResponse {
31 | status: string;
32 | message?: string;
33 | execution_time?: number;
34 | [key: string]: any;
35 | }
36 |
37 | /**
38 | * Verification Result Codes
39 | */
40 | export enum VerificationResult {
41 | Valid = 0,
42 | Invalid = 1,
43 | Disposable = 2,
44 | Catchall = 3,
45 | Unknown = 4
46 | }
47 |
48 | /**
49 | * Verification Flags
50 | */
51 | export interface VerificationFlags {
52 | has_dns: string;
53 | has_dns_mx: string;
54 | bad_syntax: string;
55 | free_email_host: string;
56 | profanity: string;
57 | role_account: string;
58 | disposable_email: string;
59 | government_host: string;
60 | academic_host: string;
61 | military_host: string;
62 | international_host: string;
63 | squatter_host: string;
64 | spelling_mistake: string;
65 | bad_dns: string;
66 | temporary_dns_error: string;
67 | connect_fails: string;
68 | accepts_all: string;
69 | contains_alias: string;
70 | contains_subdomain: string;
71 | smtp_connectable: string;
72 | spamtrap_network: string;
73 | }
74 |
75 | /**
76 | * Job Input Types
77 | */
78 | export enum JobInputType {
79 | RemoteUrl = 'remote_url',
80 | Supplied = 'supplied'
81 | }
82 |
83 | /**
84 | * Job Status Types
85 | */
86 | export enum JobStatus {
87 | UnderReview = 'under_review',
88 | Queued = 'queued',
89 | Failed = 'failed',
90 | Complete = 'complete',
91 | Running = 'running',
92 | Parsing = 'parsing',
93 | Waiting = 'waiting',
94 | WaitingAnalyzed = 'waiting_analyzed',
95 | Uploading = 'uploading'
96 | }
97 |
98 | /**
99 | * Error Types
100 | */
101 | export enum ErrorType {
102 | AuthError = 'auth_failure',
103 | BadReferrerError = 'bad_referrer',
104 | GeneralError = 'general_failure',
105 | ThrottleError = 'throttle_triggered'
106 | }
107 |
108 | /**
109 | * Single Verification Request
110 | */
111 | export interface SingleVerificationRequest {
112 | email: string;
113 | address_info?: boolean;
114 | credits_info?: boolean;
115 | timeout?: number;
116 | }
117 |
118 | /**
119 | * Single Verification Response
120 | */
121 | export interface SingleVerificationResponse extends ApiResponse {
122 | result: number;
123 | flags: string[];
124 | suggested_correction?: string;
125 | retry_token?: string;
126 | address_info?: {
127 | original: string;
128 | normalized: string;
129 | addr: string;
130 | alias: string;
131 | host: string;
132 | fqdn: string;
133 | domain: string;
134 | subdomain: string;
135 | tld: string;
136 | };
137 | credits_info?: {
138 | paid_credits_used: number;
139 | free_credits_used: number;
140 | paid_credits_remaining: number;
141 | free_credits_remaining: number;
142 | };
143 | }
144 |
145 | /**
146 | * Account Info Response
147 | */
148 | export interface AccountInfoResponse extends ApiResponse {
149 | billing_type: string;
150 | credits: {
151 | paid_credits_used: number;
152 | free_credits_used: number;
153 | paid_credits_remaining: number;
154 | free_credits_remaining: number;
155 | };
156 | job_counts: {
157 | completed: number;
158 | processing: number;
159 | queued: number;
160 | under_review: number;
161 | };
162 | }
163 |
164 | /**
165 | * POE Confirmation Request
166 | */
167 | export interface POEConfirmationRequest {
168 | email: string;
169 | transaction_id: string;
170 | confirmation_token: string;
171 | result: number;
172 | }
173 |
174 | /**
175 | * POE Confirmation Response
176 | */
177 | export interface POEConfirmationResponse extends ApiResponse {
178 | token_confirmed: boolean;
179 | token_age: number;
180 | }
181 |
182 | /**
183 | * Job Creation Request
184 | */
185 | export interface JobCreationRequest {
186 | input: string[];
187 | input_location?: string;
188 | auto_parse?: boolean | null;
189 | auto_start?: boolean | null;
190 | run_sample?: boolean | null;
191 | filename?: string;
192 | }
193 |
194 | /**
195 | * Job Creation Response
196 | */
197 | export interface JobCreationResponse extends ApiResponse {
198 | job_id: number;
199 | }
200 |
201 | /**
202 | * Job Status Response
203 | */
204 | export interface JobStatusResponse extends ApiResponse {
205 | id: number;
206 | filename: string;
207 | created_at: string;
208 | started_at: string | null;
209 | finished_at: string | null;
210 | total: {
211 | processed: number;
212 | valid: number;
213 | invalid: number;
214 | catchall: number;
215 | disposable: number;
216 | unknown: number;
217 | duplicates: number;
218 | bad_syntax: number;
219 | };
220 | job_status: string;
221 | bounce_estimate: number | null;
222 | percent_complete: number;
223 | failure_reason: string | null;
224 | }
225 |
226 | /**
227 | * Job Results Request
228 | */
229 | export interface JobResultsRequest {
230 | job_id: number;
231 | page?: number;
232 | per_page?: number;
233 | include_verification_time?: boolean;
234 | }
235 |
236 | /**
237 | * Job Results Response
238 | */
239 | export interface JobResultsResponse extends ApiResponse {
240 | total_results: number;
241 | total_pages: number;
242 | query: {
243 | job_id: number;
244 | valids: boolean;
245 | invalids: boolean;
246 | catchalls: boolean;
247 | disposables: boolean;
248 | unknowns: boolean;
249 | page: number;
250 | items_per_page: number;
251 | };
252 | results: Array<{
253 | data: Record;
254 | verification: {
255 | result: number;
256 | flags: string[];
257 | suggested_correction: string | null;
258 | address_info: {
259 | original: string;
260 | normalized: string;
261 | addr: string;
262 | alias: string;
263 | host: string;
264 | fqdn: string;
265 | domain: string;
266 | subdomain: string;
267 | tld: string;
268 | };
269 | verification_time?: number;
270 | };
271 | }>;
272 | }
273 |
274 | /**
275 | * Job Search Request
276 | */
277 | export interface JobSearchRequest {
278 | job_id?: number;
279 | page?: number;
280 | per_page?: number;
281 | }
282 |
283 | /**
284 | * Job Search Response
285 | */
286 | export interface JobSearchResponse extends ApiResponse {
287 | total_results: number;
288 | total_pages: number;
289 | query: {
290 | page: number;
291 | items_per_page: number;
292 | };
293 | results: Array<{
294 | id: number;
295 | job_status: string;
296 | filename: string;
297 | created_at: string;
298 | started_at: string | null;
299 | finished_at: string | null;
300 | total: {
301 | processed: number;
302 | valid: number;
303 | invalid: number;
304 | catchall: number;
305 | disposable: number;
306 | unknown: number;
307 | duplicates: number;
308 | bad_syntax: number;
309 | };
310 | bounce_estimate: number | null;
311 | percent_complete: number;
312 | failure_reason: string | null;
313 | }>;
314 | }
315 |
316 | /**
317 | * Job Delete Response
318 | */
319 | export interface JobDeleteResponse extends ApiResponse {
320 | job_id: number;
321 | }
322 |
323 | /**
324 | * Job Download Request
325 | */
326 | export interface JobDownloadRequest {
327 | job_id: number;
328 | }
329 |
330 | /**
331 | * Resource Class Constructor
332 | */
333 | export interface ResourceConstructor {
334 | new (client: any): any;
335 | }
336 |
337 | /**
338 | * Resources Map
339 | */
340 | export interface ResourcesMap {
341 | [key: string]: ResourceConstructor;
342 | }
343 |
--------------------------------------------------------------------------------
/test-cjs.cjs:
--------------------------------------------------------------------------------
1 | // CommonJS test
2 | const NeverBounce = require('./dist/cjs/src/index.js');
3 |
4 | console.log('CommonJS require successful!');
5 | console.log('NeverBounce version:', NeverBounce.default);
6 |
--------------------------------------------------------------------------------
/test-esm.js:
--------------------------------------------------------------------------------
1 | // ESM test
2 | import NeverBounce from './dist/esm/src/index.js';
3 |
4 | console.log('ESM import successful!');
5 | console.log('NeverBounce:', NeverBounce);
6 |
--------------------------------------------------------------------------------
/tests/Account.test.ts:
--------------------------------------------------------------------------------
1 | import Account from '../src/Account';
2 | import { RequestOptions } from '../src/types.js';
3 | import { jest } from '@jest/globals';
4 |
5 | // We don't need to mock HttpsClient directly
6 | // Instead, we'll mock the request method on the Account instance
7 |
8 | describe('Account', () => {
9 | let account: any;
10 | let mockClient: any;
11 |
12 | beforeEach(() => {
13 | // Clear all mock implementations and calls before each test
14 | jest.clearAllMocks();
15 |
16 | // Create a mock NeverBounce client
17 | mockClient = {
18 | getConfig: jest.fn().mockReturnValue({
19 | apiKey: 'test-api-key',
20 | apiVersion: 'v4.2',
21 | timeout: 30000,
22 | opts: {
23 | acceptedType: 'application/json',
24 | host: 'api.neverbounce.com',
25 | port: 443,
26 | headers: {
27 | 'Content-Type': 'application/json',
28 | 'User-Agent': 'NeverBounce-Node/5.0.0'
29 | }
30 | }
31 | }),
32 | getRequestOpts: jest.fn().mockImplementation((params: any) => {
33 | return {
34 | acceptedType: 'application/json',
35 | host: 'api.neverbounce.com',
36 | port: 443,
37 | headers: {
38 | 'Content-Type': 'application/json',
39 | 'User-Agent': 'NeverBounce-Node/5.0.0',
40 | ...((params && params.headers) || {})
41 | },
42 | ...(params || {})
43 | } as RequestOptions;
44 | })
45 | };
46 |
47 | // Create a new Account instance for each test
48 | account = new Account(mockClient);
49 |
50 | // Mock the request method directly on the Account instance
51 | account.request = jest.fn();
52 | });
53 |
54 | describe('info()', () => {
55 | it('should call request with correct parameters', async () => {
56 | // Arrange
57 | const mockResponse = {
58 | id: '12345',
59 | name: 'Test Account',
60 | status: 'active',
61 | createdAt: '2023-01-01T00:00:00Z'
62 | };
63 |
64 | account.request.mockResolvedValueOnce(mockResponse);
65 |
66 | // Act
67 | const result = await account.info();
68 |
69 | // Assert
70 | expect(account.request).toHaveBeenCalledTimes(1);
71 | expect(account.request).toHaveBeenCalledWith({
72 | method: 'GET',
73 | path: 'account/info'
74 | });
75 | expect(result).toEqual(mockResponse);
76 | });
77 |
78 | it('should propagate errors from the request method', async () => {
79 | // Arrange
80 | const mockError = new Error('Network error');
81 | account.request.mockRejectedValueOnce(mockError);
82 |
83 | // Act & Assert
84 | await expect(account.info()).rejects.toThrow('Network error');
85 | expect(account.request).toHaveBeenCalledTimes(1);
86 | });
87 |
88 | it('should handle empty response correctly', async () => {
89 | // Arrange
90 | const mockEmptyResponse = {};
91 | account.request.mockResolvedValueOnce(mockEmptyResponse);
92 |
93 | // Act
94 | const result = await account.info();
95 |
96 | // Assert
97 | expect(result).toEqual({});
98 | expect(account.request).toHaveBeenCalledTimes(1);
99 | });
100 |
101 | it('should return the exact response from request method', async () => {
102 | // Arrange
103 | const mockDetailedResponse = {
104 | id: 'acc_123456789',
105 | name: 'Premium Account',
106 | status: 'active',
107 | features: ['feature1', 'feature2'],
108 | limits: {
109 | maxUsers: 10,
110 | maxStorage: '5GB'
111 | },
112 | createdAt: '2023-01-01T00:00:00Z',
113 | updatedAt: '2023-06-15T12:34:56Z'
114 | };
115 |
116 | account.request.mockResolvedValueOnce(mockDetailedResponse);
117 |
118 | // Act
119 | const result = await account.info();
120 |
121 | // Assert
122 | expect(result).toEqual(mockDetailedResponse);
123 | expect(account.request).toHaveBeenCalledWith({
124 | method: 'GET',
125 | path: 'account/info'
126 | });
127 | });
128 | });
129 | });
130 |
--------------------------------------------------------------------------------
/tests/Errors.test.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import { describe, it, expect } from '@jest/globals';
4 | import _Error from '../src/Errors.js';
5 |
6 | describe('_Error', () => {
7 | describe('constructor', () => {
8 | it('should create an error with default values when no parameters are provided', () => {
9 | const error = new _Error();
10 |
11 | expect(error.name).toBe('NeverBounce Error');
12 | expect(error.type).toBe('GeneralError');
13 | expect(error.message).toBe('General Error');
14 | expect(error instanceof Error).toBe(true);
15 | });
16 |
17 | it('should create an error with the provided type and default message', () => {
18 | const error = new _Error('AuthError');
19 |
20 | expect(error.name).toBe('NeverBounce Error');
21 | expect(error.type).toBe('AuthError');
22 | expect(error.message).toBe('General Error');
23 | });
24 |
25 | it('should create an error with the provided type and message', () => {
26 | const error = new _Error('AuthError', 'Invalid API key');
27 |
28 | expect(error.name).toBe('NeverBounce Error');
29 | expect(error.type).toBe('AuthError');
30 | expect(error.message).toBe('Invalid API key');
31 | });
32 | });
33 |
34 | describe('static properties', () => {
35 | it('should expose error type constants', () => {
36 | expect(_Error.AuthError).toBe('AuthError');
37 | expect(_Error.BadReferrerError).toBe('BadReferrerError');
38 | expect(_Error.GeneralError).toBe('GeneralError');
39 | expect(_Error.ThrottleError).toBe('ThrottleError');
40 | });
41 |
42 | it('should have a lookup table for error types', () => {
43 | expect(_Error._lut['auth_failure']).toBe(_Error.AuthError);
44 | expect(_Error._lut['bad_referrer']).toBe(_Error.BadReferrerError);
45 | expect(_Error._lut['general_failure']).toBe(_Error.GeneralError);
46 | expect(_Error._lut['throttle_triggered']).toBe(_Error.ThrottleError);
47 | });
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/tests/Jobs.test.ts:
--------------------------------------------------------------------------------
1 | import Jobs from '../src/Jobs.js';
2 | import { RequestOptions } from '../src/types.js';
3 | import { jest } from '@jest/globals';
4 |
5 | // We don't need to mock HttpsClient directly
6 | // Instead, we'll mock the request method on the Jobs instance
7 |
8 | describe('Jobs', () => {
9 | let jobs: any;
10 | let mockClient: any;
11 |
12 | beforeEach(() => {
13 | // Clear all mocks
14 | jest.clearAllMocks();
15 |
16 | // Create a mock NeverBounce client
17 | mockClient = {
18 | getConfig: jest.fn().mockReturnValue({
19 | apiKey: 'test-api-key',
20 | apiVersion: 'v4.2',
21 | timeout: 30000,
22 | opts: {
23 | acceptedType: 'application/json',
24 | host: 'api.neverbounce.com',
25 | port: 443,
26 | headers: {
27 | 'Content-Type': 'application/json',
28 | 'User-Agent': 'NeverBounce-Node/5.0.0'
29 | }
30 | }
31 | }),
32 | getRequestOpts: jest.fn().mockImplementation((params: any) => {
33 | return {
34 | acceptedType: 'application/json',
35 | host: 'api.neverbounce.com',
36 | port: 443,
37 | headers: {
38 | 'Content-Type': 'application/json',
39 | 'User-Agent': 'NeverBounce-Node/5.0.0',
40 | ...((params && params.headers) || {})
41 | },
42 | ...(params || {})
43 | } as RequestOptions;
44 | })
45 | };
46 |
47 | // Create new Jobs instance
48 | jobs = new Jobs(mockClient);
49 |
50 | // Mock the request method directly on the Jobs instance
51 | jobs.request = jest.fn();
52 | });
53 |
54 | describe('search', () => {
55 | it('should call request with correct parameters when no query is provided', async () => {
56 | // Arrange
57 | const mockResponse = { status: 'success', results: [] };
58 | jobs.request.mockResolvedValueOnce(mockResponse);
59 |
60 | // Act
61 | const result = await jobs.search();
62 |
63 | // Assert
64 | expect(jobs.request).toHaveBeenCalledWith(
65 | { method: 'GET', path: 'jobs/search' },
66 | {}
67 | );
68 | expect(result).toBe(mockResponse);
69 | });
70 |
71 | it('should call request with correct parameters when query is provided', async () => {
72 | // Arrange
73 | const mockQuery = { page: 1, per_page: 10 };
74 | const mockResponse = { status: 'success', results: [] };
75 | jobs.request.mockResolvedValueOnce(mockResponse);
76 |
77 | // Act
78 | const result = await jobs.search(mockQuery);
79 |
80 | // Assert
81 | expect(jobs.request).toHaveBeenCalledWith(
82 | { method: 'GET', path: 'jobs/search' },
83 | mockQuery
84 | );
85 | expect(result).toBe(mockResponse);
86 | });
87 | });
88 |
89 | describe('create', () => {
90 | it('should handle array input correctly', async () => {
91 | // Arrange
92 | const mockInput = ['test@example.com', 'test2@example.com'];
93 | const mockResponse = { status: 'success', job_id: 123 };
94 | jobs.request.mockResolvedValueOnce(mockResponse);
95 |
96 | // Act
97 | const result = await jobs.create(mockInput);
98 |
99 | // Assert
100 | expect(jobs.request).toHaveBeenCalledWith(
101 | { method: 'POST', path: 'jobs/create' },
102 | {
103 | input: mockInput,
104 | input_location: undefined,
105 | filename: undefined,
106 | run_sample: null,
107 | auto_start: null,
108 | auto_parse: null,
109 | allow_manual_review: null,
110 | callback_url: null,
111 | callback_headers: null
112 | }
113 | );
114 | expect(result).toBe(mockResponse);
115 | });
116 |
117 | it('should handle string input correctly', async () => {
118 | // Arrange
119 | const mockInput = 'https://example.com/emails.csv';
120 | const mockResponse = { status: 'success', job_id: 123 };
121 | jobs.request.mockResolvedValueOnce(mockResponse);
122 |
123 | // Act
124 | const result = await jobs.create(mockInput, Jobs.remote);
125 |
126 | // Assert
127 | expect(jobs.request).toHaveBeenCalledWith(
128 | { method: 'POST', path: 'jobs/create' },
129 | {
130 | input: [mockInput],
131 | input_location: Jobs.remote,
132 | filename: undefined,
133 | run_sample: null,
134 | auto_start: null,
135 | auto_parse: null,
136 | allow_manual_review: null,
137 | callback_url: null,
138 | callback_headers: null
139 | }
140 | );
141 | expect(result).toBe(mockResponse);
142 | });
143 |
144 | it('should handle all optional parameters correctly', async () => {
145 | // Arrange
146 | const mockInput = ['test@example.com'];
147 | const mockFilename = 'test-job.csv';
148 | const mockCallbackHeaders = { 'X-Custom-Header': 'value' };
149 | const mockCallbackUrl = 'https://example.com/callback';
150 | const mockResponse = { status: 'success', job_id: 123 };
151 | jobs.request.mockResolvedValueOnce(mockResponse);
152 |
153 | // Act
154 | const result = await jobs.create(
155 | mockInput,
156 | Jobs.supplied,
157 | mockFilename,
158 | true, // runsample
159 | true, // autoparse
160 | true, // autostart
161 | true, // historicalData
162 | true, // allowManualReview
163 | mockCallbackUrl,
164 | mockCallbackHeaders
165 | );
166 |
167 | // Assert
168 | expect(jobs.request).toHaveBeenCalledWith(
169 | { method: 'POST', path: 'jobs/create' },
170 | {
171 | input: mockInput,
172 | input_location: Jobs.supplied,
173 | filename: mockFilename,
174 | run_sample: true,
175 | auto_start: true,
176 | auto_parse: true,
177 | request_meta_data: { leverage_historical_data: 1 },
178 | allow_manual_review: true,
179 | callback_url: mockCallbackUrl,
180 | callback_headers: mockCallbackHeaders
181 | }
182 | );
183 | expect(result).toBe(mockResponse);
184 | });
185 |
186 | it('should set historical data to 0 when false', async () => {
187 | // Arrange
188 | const mockInput = ['test@example.com'];
189 | const mockResponse = { status: 'success', job_id: 123 };
190 | jobs.request.mockResolvedValueOnce(mockResponse);
191 |
192 | // Act
193 | const result = await jobs.create(
194 | mockInput,
195 | undefined,
196 | undefined,
197 | undefined,
198 | undefined,
199 | undefined,
200 | false // historicalData
201 | );
202 |
203 | // Assert
204 | expect(jobs.request).toHaveBeenCalledWith(
205 | { method: 'POST', path: 'jobs/create' },
206 | expect.objectContaining({
207 | request_meta_data: { leverage_historical_data: 0 }
208 | })
209 | );
210 | expect(result).toBe(mockResponse);
211 | });
212 | });
213 |
214 | describe('parse', () => {
215 | it('should call request with correct parameters', async () => {
216 | // Arrange
217 | const jobId = 123;
218 | const mockResponse = { status: 'success' };
219 | jobs.request.mockResolvedValueOnce(mockResponse);
220 |
221 | // Act
222 | const result = await jobs.parse(jobId);
223 |
224 | // Assert
225 | expect(jobs.request).toHaveBeenCalledWith(
226 | { method: 'POST', path: 'jobs/parse' },
227 | { job_id: jobId, auto_start: undefined }
228 | );
229 | expect(result).toBe(mockResponse);
230 | });
231 |
232 | it('should include autostart when provided', async () => {
233 | // Arrange
234 | const jobId = 123;
235 | const autostart = true;
236 | const mockResponse = { status: 'success' };
237 | jobs.request.mockResolvedValueOnce(mockResponse);
238 |
239 | // Act
240 | const result = await jobs.parse(jobId, autostart);
241 |
242 | // Assert
243 | expect(jobs.request).toHaveBeenCalledWith(
244 | { method: 'POST', path: 'jobs/parse' },
245 | { job_id: jobId, auto_start: autostart }
246 | );
247 | expect(result).toBe(mockResponse);
248 | });
249 | });
250 |
251 | describe('start', () => {
252 | it('should call request with correct parameters', async () => {
253 | // Arrange
254 | const jobId = 123;
255 | const mockResponse = { status: 'success' };
256 | jobs.request.mockResolvedValueOnce(mockResponse);
257 |
258 | // Act
259 | const result = await jobs.start(jobId);
260 |
261 | // Assert
262 | expect(jobs.request).toHaveBeenCalledWith(
263 | { method: 'POST', path: 'jobs/start' },
264 | { job_id: jobId, run_sample: undefined, allow_manual_review: undefined }
265 | );
266 | expect(result).toBe(mockResponse);
267 | });
268 |
269 | it('should include optional parameters when provided', async () => {
270 | // Arrange
271 | const jobId = 123;
272 | const runSample = true;
273 | const allowManualReview = true;
274 | const mockResponse = { status: 'success' };
275 | jobs.request.mockResolvedValueOnce(mockResponse);
276 |
277 | // Act
278 | const result = await jobs.start(jobId, runSample, allowManualReview);
279 |
280 | // Assert
281 | expect(jobs.request).toHaveBeenCalledWith(
282 | { method: 'POST', path: 'jobs/start' },
283 | { job_id: jobId, run_sample: runSample, allow_manual_review: allowManualReview }
284 | );
285 | expect(result).toBe(mockResponse);
286 | });
287 | });
288 |
289 | describe('status', () => {
290 | it('should call request with correct parameters', async () => {
291 | // Arrange
292 | const jobId = 123;
293 | const mockResponse = {
294 | status: 'success',
295 | job_status: 'complete',
296 | total_records: 100
297 | };
298 | jobs.request.mockResolvedValueOnce(mockResponse);
299 |
300 | // Act
301 | const result = await jobs.status(jobId);
302 |
303 | // Assert
304 | expect(jobs.request).toHaveBeenCalledWith(
305 | { method: 'GET', path: 'jobs/status' },
306 | { job_id: jobId }
307 | );
308 | expect(result).toBe(mockResponse);
309 | });
310 | });
311 |
312 | describe('results', () => {
313 | it('should call request with correct parameters when no query is provided', async () => {
314 | // Arrange
315 | const jobId = 123;
316 | const mockResponse = {
317 | status: 'success',
318 | results: []
319 | };
320 | jobs.request.mockResolvedValueOnce(mockResponse);
321 |
322 | // Act
323 | const result = await jobs.results(jobId);
324 |
325 | // Assert
326 | expect(jobs.request).toHaveBeenCalledWith(
327 | { method: 'GET', path: 'jobs/results' },
328 | { job_id: jobId }
329 | );
330 | expect(result).toBe(mockResponse);
331 | });
332 |
333 | it('should call request with correct parameters when query is provided', async () => {
334 | // Arrange
335 | const jobId = 123;
336 | const query = { page: 1, per_page: 10 };
337 | const mockResponse = {
338 | status: 'success',
339 | results: []
340 | };
341 | jobs.request.mockResolvedValueOnce(mockResponse);
342 |
343 | // Act
344 | const result = await jobs.results(jobId, query);
345 |
346 | // Assert
347 | expect(jobs.request).toHaveBeenCalledWith(
348 | { method: 'GET', path: 'jobs/results' },
349 | { job_id: jobId, ...query }
350 | );
351 | expect(result).toBe(mockResponse);
352 | });
353 | });
354 |
355 | describe('download', () => {
356 | it('should call request with correct parameters when no query is provided', async () => {
357 | // Arrange
358 | const jobId = 123;
359 | const mockResponse = 'email,result\ntest@example.com,valid';
360 | jobs.request.mockResolvedValueOnce(mockResponse);
361 |
362 | // Act
363 | const result = await jobs.download(jobId);
364 |
365 | // Assert
366 | expect(jobs.request).toHaveBeenCalledWith(
367 | {
368 | acceptedType: 'application/octet-stream',
369 | method: 'GET',
370 | path: 'jobs/download'
371 | },
372 | { job_id: jobId }
373 | );
374 | expect(result).toBe(mockResponse);
375 | });
376 |
377 | it('should call request with correct parameters when query is provided', async () => {
378 | // Arrange
379 | const jobId = 123;
380 | const query = { bounce_flags: true };
381 | const mockResponse = 'email,result,bounce_flags\ntest@example.com,valid,none';
382 | jobs.request.mockResolvedValueOnce(mockResponse);
383 |
384 | // Act
385 | const result = await jobs.download(jobId, query);
386 |
387 | // Assert
388 | expect(jobs.request).toHaveBeenCalledWith(
389 | {
390 | acceptedType: 'application/octet-stream',
391 | method: 'GET',
392 | path: 'jobs/download'
393 | },
394 | { job_id: jobId, ...query }
395 | );
396 | expect(result).toBe(mockResponse);
397 | });
398 | });
399 |
400 | describe('delete', () => {
401 | it('should call request with correct parameters', async () => {
402 | // Arrange
403 | const jobId = 123;
404 | const mockResponse = { status: 'success' };
405 | jobs.request.mockResolvedValueOnce(mockResponse);
406 |
407 | // Act
408 | const result = await jobs.delete(jobId);
409 |
410 | // Assert
411 | expect(jobs.request).toHaveBeenCalledWith(
412 | { method: 'POST', path: 'jobs/delete' },
413 | { job_id: jobId }
414 | );
415 | expect(result).toBe(mockResponse);
416 | });
417 | });
418 |
419 | describe('Static properties', () => {
420 | it('should have correct input type constants', () => {
421 | expect(Jobs.remote).toBe('remote_url');
422 | expect(Jobs.supplied).toBe('supplied');
423 | });
424 |
425 | it('should have correct helpers object', () => {
426 | expect(Jobs.helpers.inputType.remote).toBe('remote_url');
427 | expect(Jobs.helpers.inputType.supplied).toBe('supplied');
428 | expect(Jobs.helpers.status.under_review).toBe('under_review');
429 | expect(Jobs.helpers.status.queued).toBe('queued');
430 | expect(Jobs.helpers.status.failed).toBe('failed');
431 | expect(Jobs.helpers.status.complete).toBe('complete');
432 | expect(Jobs.helpers.status.running).toBe('running');
433 | expect(Jobs.helpers.status.parsing).toBe('parsing');
434 | expect(Jobs.helpers.status.waiting).toBe('waiting');
435 | expect(Jobs.helpers.status.waiting_analyzed).toBe('waiting_analyzed');
436 | expect(Jobs.helpers.status.uploading).toBe('uploading');
437 | });
438 | });
439 | });
440 |
--------------------------------------------------------------------------------
/tests/NeverBounce.test.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import { describe, it, expect } from '@jest/globals';
4 | import NeverBounce from '../src/NeverBounce.js';
5 | import Account from '../src/Account.js';
6 | import Jobs from '../src/Jobs.js';
7 | import POE from '../src/POE.js';
8 | import Single from '../src/Single.js';
9 | import { NeverBounceConfig } from '../src/types.js';
10 |
11 | describe('NeverBounce', () => {
12 | describe('constructor', () => {
13 | it('should initialize with default config when no config is provided', () => {
14 | const nb = new NeverBounce();
15 | const config = nb.getConfig();
16 |
17 | expect(config.apiVersion).toBe('v4.2');
18 | expect(config.apiKey).toBeNull();
19 | expect(config.timeout).toBe(30000);
20 | expect(config.opts.host).toBe('api.neverbounce.com');
21 | expect(config.opts.port).toBe(443);
22 | expect(config.opts.headers['Content-Type']).toBe('application/json');
23 | expect(config.opts.headers['User-Agent']).toBe('NeverBounce-Node/5.0.0');
24 | });
25 |
26 | it('should merge provided config with default config', () => {
27 | const customConfig: Partial = {
28 | apiKey: 'test-api-key',
29 | timeout: 60000,
30 | opts: {
31 | acceptedType: 'application/json',
32 | host: 'custom.api.neverbounce.com',
33 | port: 443,
34 | headers: {
35 | 'Custom-Header': 'custom-value'
36 | }
37 | }
38 | };
39 |
40 | const nb = new NeverBounce(customConfig);
41 | const config = nb.getConfig();
42 |
43 | expect(config.apiKey).toBe('test-api-key');
44 | expect(config.timeout).toBe(60000);
45 | expect(config.opts.host).toBe('custom.api.neverbounce.com');
46 | expect(config.opts.port).toBe(443); // Default value
47 | expect(config.opts.headers['Content-Type']).toBe('application/json'); // Default value
48 | expect(config.opts.headers['Custom-Header']).toBe('custom-value'); // Custom value
49 | });
50 |
51 | it('should initialize resource instances', () => {
52 | const nb = new NeverBounce();
53 |
54 | expect(nb.account).toBeInstanceOf(Account);
55 | expect(nb.jobs).toBeInstanceOf(Jobs);
56 | expect(nb.poe).toBeInstanceOf(POE);
57 | expect(nb.single).toBeInstanceOf(Single);
58 | });
59 | });
60 |
61 | describe('getRequestOpts', () => {
62 | it('should return request options with merged headers', () => {
63 | const nb = new NeverBounce({
64 | opts: {
65 | acceptedType: 'application/json',
66 | host: 'api.neverbounce.com',
67 | port: 443,
68 | headers: {
69 | 'Default-Header': 'default-value'
70 | }
71 | }
72 | });
73 |
74 | const opts = nb.getRequestOpts({
75 | method: 'POST',
76 | path: 'test/path',
77 | headers: {
78 | 'Custom-Header': 'custom-value'
79 | }
80 | });
81 |
82 | expect(opts.method).toBe('POST');
83 | expect(opts.path).toBe('test/path');
84 | expect(opts.headers['Default-Header']).toBe('default-value');
85 | expect(opts.headers['Custom-Header']).toBe('custom-value');
86 | expect(opts.headers['Content-Type']).toBe('application/json'); // From default config
87 | });
88 | });
89 |
90 | describe('updateConfig', () => {
91 | it('should update config values', () => {
92 | const nb = new NeverBounce();
93 |
94 | nb.updateConfig({
95 | apiKey: 'updated-api-key',
96 | apiVersion: 'v5.0',
97 | timeout: 45000,
98 | opts: {
99 | acceptedType: 'application/json',
100 | host: 'updated.api.neverbounce.com',
101 | port: 443,
102 | headers: {
103 | 'Updated-Header': 'updated-value'
104 | }
105 | }
106 | });
107 |
108 | const config = nb.getConfig();
109 |
110 | expect(config.apiKey).toBe('updated-api-key');
111 | expect(config.apiVersion).toBe('v5.0');
112 | expect(config.timeout).toBe(45000);
113 | expect(config.opts.host).toBe('updated.api.neverbounce.com');
114 | expect(config.opts.headers['Updated-Header']).toBe('updated-value');
115 | expect(config.opts.headers['Content-Type']).toBe('application/json'); // Preserved from default
116 | });
117 |
118 | it('should only update provided config values', () => {
119 | const nb = new NeverBounce({
120 | apiKey: 'initial-api-key',
121 | timeout: 30000
122 | });
123 |
124 | nb.updateConfig({
125 | timeout: 45000
126 | });
127 |
128 | const config = nb.getConfig();
129 |
130 | expect(config.apiKey).toBe('initial-api-key'); // Unchanged
131 | expect(config.timeout).toBe(45000); // Updated
132 | });
133 | });
134 |
135 | describe('setApiKey', () => {
136 | it('should update the API key', () => {
137 | const nb = new NeverBounce();
138 |
139 | nb.setApiKey('new-api-key');
140 |
141 | expect(nb.getConfig().apiKey).toBe('new-api-key');
142 | });
143 | });
144 |
145 | describe('setHost', () => {
146 | it('should update the API host', () => {
147 | const nb = new NeverBounce();
148 |
149 | nb.setHost('new.api.neverbounce.com');
150 |
151 | expect(nb.getConfig().opts.host).toBe('new.api.neverbounce.com');
152 | });
153 | });
154 |
155 | describe('static properties', () => {
156 | it('should expose result helpers', () => {
157 | expect(NeverBounce.result.valid).toBe(0);
158 | expect(NeverBounce.result.invalid).toBe(1);
159 | expect(NeverBounce.result.disposable).toBe(2);
160 | expect(NeverBounce.result.catchall).toBe(3);
161 | expect(NeverBounce.result.unknown).toBe(4);
162 | });
163 |
164 | it('should expose job helpers', () => {
165 | expect(NeverBounce.job.inputType.remote).toBe('remote_url');
166 | expect(NeverBounce.job.inputType.supplied).toBe('supplied');
167 | expect(NeverBounce.job.status.complete).toBe('complete');
168 | expect(NeverBounce.job.status.failed).toBe('failed');
169 | expect(NeverBounce.job.status.queued).toBe('queued');
170 | expect(NeverBounce.job.status.running).toBe('running');
171 | expect(NeverBounce.job.status.parsing).toBe('parsing');
172 | expect(NeverBounce.job.status.waiting).toBe('waiting');
173 | expect(NeverBounce.job.status.waiting_analyzed).toBe('waiting_analyzed');
174 | expect(NeverBounce.job.status.uploading).toBe('uploading');
175 | expect(NeverBounce.job.status.under_review).toBe('under_review');
176 | });
177 |
178 | it('should expose error types', () => {
179 | expect(NeverBounce.errors.AuthError).toBe('AuthError');
180 | expect(NeverBounce.errors.BadReferrerError).toBe('BadReferrerError');
181 | expect(NeverBounce.errors.GeneralError).toBe('GeneralError');
182 | expect(NeverBounce.errors.ThrottleError).toBe('ThrottleError');
183 | });
184 | });
185 | });
186 |
--------------------------------------------------------------------------------
/tests/POE.test.ts:
--------------------------------------------------------------------------------
1 | import POE from '../src/POE.js';
2 | import { RequestOptions } from '../src/types.js';
3 | import { jest } from '@jest/globals';
4 |
5 | // We don't need to mock HttpsClient directly
6 | // Instead, we'll mock the request method on the POE instance
7 |
8 | describe('POE', () => {
9 | let poe: any;
10 | let mockClient: any;
11 |
12 | beforeEach(() => {
13 | // Clear all mocks
14 | jest.clearAllMocks();
15 |
16 | mockClient = {
17 | getConfig: jest.fn().mockReturnValue({
18 | apiKey: 'test-api-key',
19 | apiVersion: 'v4.2',
20 | timeout: 30000,
21 | opts: {
22 | acceptedType: 'application/json',
23 | host: 'api.neverbounce.com',
24 | port: 443,
25 | headers: {
26 | 'Content-Type': 'application/json',
27 | 'User-Agent': 'NeverBounce-Node/5.0.0'
28 | }
29 | }
30 | }),
31 | getRequestOpts: jest.fn().mockImplementation((params: any) => {
32 | return {
33 | acceptedType: 'application/json',
34 | host: 'api.neverbounce.com',
35 | port: 443,
36 | headers: {
37 | 'Content-Type': 'application/json',
38 | 'User-Agent': 'NeverBounce-Node/5.0.0',
39 | ...((params && params.headers) || {})
40 | },
41 | ...(params || {})
42 | } as RequestOptions;
43 | })
44 | };
45 |
46 | // Create a new POE instance for each test
47 | poe = new POE(mockClient);
48 |
49 | // Mock the request method directly on the POE instance
50 | poe.request = jest.fn();
51 | });
52 |
53 | describe('confirm', () => {
54 | it('should call request with correct parameters', async () => {
55 | // Arrange
56 | const email = 'test@example.com';
57 | const result = 0;
58 | const confirmationToken = 'abc123';
59 | const transactionId = 'txn456';
60 |
61 | const mockResponse = {
62 | status: 'success',
63 | token_confirmed: true,
64 | token_age: '2023-12-01 12:34:56'
65 | };
66 |
67 | poe.request.mockResolvedValueOnce(mockResponse);
68 |
69 | // Act
70 | const response = await poe.confirm(email, result, confirmationToken, transactionId);
71 |
72 | // Assert
73 | expect(poe.request).toHaveBeenCalledTimes(1);
74 | expect(poe.request).toHaveBeenCalledWith(
75 | {
76 | method: 'POST',
77 | path: 'poe/confirm'
78 | },
79 | {
80 | email: email,
81 | result: result,
82 | confirmation_token: confirmationToken,
83 | transaction_id: transactionId
84 | }
85 | );
86 | expect(response).toEqual(mockResponse);
87 | });
88 |
89 | it('should handle different result values', async () => {
90 | // Arrange
91 | const email = 'test@example.com';
92 | const result = 1; // Different result value
93 | const confirmationToken = 'abc123';
94 | const transactionId = 'txn456';
95 |
96 | const mockResponse = {
97 | status: 'success',
98 | token_confirmed: true,
99 | token_age: '2023-12-01 12:34:56'
100 | };
101 |
102 | poe.request.mockResolvedValueOnce(mockResponse);
103 |
104 | // Act
105 | const response = await poe.confirm(email, result, confirmationToken, transactionId);
106 |
107 | // Assert
108 | expect(poe.request).toHaveBeenCalledWith(
109 | {
110 | method: 'POST',
111 | path: 'poe/confirm'
112 | },
113 | {
114 | email: email,
115 | result: result,
116 | confirmation_token: confirmationToken,
117 | transaction_id: transactionId
118 | }
119 | );
120 | expect(response).toEqual(mockResponse);
121 | });
122 |
123 | it('should propagate errors from request method', async () => {
124 | // Arrange
125 | const email = 'test@example.com';
126 | const result = 0;
127 | const confirmationToken = 'abc123';
128 | const transactionId = 'txn456';
129 |
130 | const mockError = new Error('API Error');
131 | poe.request.mockRejectedValueOnce(mockError);
132 |
133 | // Act & Assert
134 | await expect(poe.confirm(email, result, confirmationToken, transactionId))
135 | .rejects.toThrow('API Error');
136 |
137 | expect(poe.request).toHaveBeenCalledTimes(1);
138 | });
139 |
140 | it('should handle successful confirmation with false token_confirmed', async () => {
141 | // Arrange
142 | const email = 'test@example.com';
143 | const result = 0;
144 | const confirmationToken = 'abc123';
145 | const transactionId = 'txn456';
146 |
147 | const mockResponse = {
148 | status: 'success',
149 | token_confirmed: false,
150 | message: 'Token has expired'
151 | };
152 |
153 | poe.request.mockResolvedValueOnce(mockResponse);
154 |
155 | // Act
156 | const response = await poe.confirm(email, result, confirmationToken, transactionId);
157 |
158 | // Assert
159 | expect(poe.request).toHaveBeenCalledTimes(1);
160 | expect(response).toEqual(mockResponse);
161 | });
162 |
163 | it('should handle confirmation with non-standard response fields', async () => {
164 | // Arrange
165 | const email = 'test@example.com';
166 | const result = 2;
167 | const confirmationToken = 'abc123';
168 | const transactionId = 'txn456';
169 |
170 | const mockResponse = {
171 | status: 'success',
172 | token_confirmed: true,
173 | token_age: '2023-12-01 12:34:56',
174 | additional_field: 'extra data'
175 | };
176 |
177 | poe.request.mockResolvedValueOnce(mockResponse);
178 |
179 | // Act
180 | const response = await poe.confirm(email, result, confirmationToken, transactionId);
181 |
182 | // Assert
183 | expect(poe.request).toHaveBeenCalledTimes(1);
184 | expect(response).toEqual(mockResponse);
185 | expect(response.additional_field).toBe('extra data');
186 | });
187 |
188 | it('should handle special characters in email address', async () => {
189 | // Arrange
190 | const email = 'test+special@example.com'; // Email with + character
191 | const result = 0;
192 | const confirmationToken = 'abc123';
193 | const transactionId = 'txn456';
194 |
195 | const mockResponse = {
196 | status: 'success',
197 | token_confirmed: true,
198 | token_age: '2023-12-01 12:34:56'
199 | };
200 |
201 | poe.request.mockResolvedValueOnce(mockResponse);
202 |
203 | // Act
204 | const response = await poe.confirm(email, result, confirmationToken, transactionId);
205 |
206 | // Assert
207 | expect(poe.request).toHaveBeenCalledWith(
208 | {
209 | method: 'POST',
210 | path: 'poe/confirm'
211 | },
212 | {
213 | email: email,
214 | result: result,
215 | confirmation_token: confirmationToken,
216 | transaction_id: transactionId
217 | }
218 | );
219 | expect(response).toEqual(mockResponse);
220 | });
221 |
222 | it('should handle long tokens and transaction IDs', async () => {
223 | // Arrange
224 | const email = 'test@example.com';
225 | const result = 0;
226 | const confirmationToken = 'a'.repeat(100); // Very long token
227 | const transactionId = 'b'.repeat(100); // Very long transaction ID
228 |
229 | const mockResponse = {
230 | status: 'success',
231 | token_confirmed: true,
232 | token_age: '2023-12-01 12:34:56'
233 | };
234 |
235 | poe.request.mockResolvedValueOnce(mockResponse);
236 |
237 | // Act
238 | const response = await poe.confirm(email, result, confirmationToken, transactionId);
239 |
240 | // Assert
241 | expect(poe.request).toHaveBeenCalledWith(
242 | {
243 | method: 'POST',
244 | path: 'poe/confirm'
245 | },
246 | {
247 | email: email,
248 | result: result,
249 | confirmation_token: confirmationToken,
250 | transaction_id: transactionId
251 | }
252 | );
253 | expect(response).toEqual(mockResponse);
254 | });
255 | });
256 | });
257 |
--------------------------------------------------------------------------------
/tests/Single.test.ts:
--------------------------------------------------------------------------------
1 | import Single from '../src/Single';
2 | import { RequestOptions } from '../src/types';
3 | import { jest } from '@jest/globals';
4 |
5 | // We don't need to mock HttpsClient directly
6 | // Instead, we'll mock the request method on the Single instance
7 |
8 | describe('Single', () => {
9 | let single: any;
10 | let mockResponse: any;
11 | let mockClient: any;
12 |
13 | beforeEach(() => {
14 | // Clear all mocks before each test
15 | jest.clearAllMocks();
16 |
17 | // Create a mock NeverBounce client
18 | mockClient = {
19 | getConfig: jest.fn().mockReturnValue({
20 | apiKey: 'test-api-key',
21 | apiVersion: 'v4.2',
22 | timeout: 30000,
23 | opts: {
24 | acceptedType: 'application/json',
25 | host: 'api.neverbounce.com',
26 | port: 443,
27 | headers: {
28 | 'Content-Type': 'application/json',
29 | 'User-Agent': 'NeverBounce-Node/5.0.0'
30 | }
31 | }
32 | }),
33 | getRequestOpts: jest.fn().mockImplementation((params: any) => {
34 | return {
35 | acceptedType: 'application/json',
36 | host: 'api.neverbounce.com',
37 | port: 443,
38 | headers: {
39 | 'Content-Type': 'application/json',
40 | 'User-Agent': 'NeverBounce-Node/5.0.0',
41 | ...((params && params.headers) || {})
42 | },
43 | ...(params || {})
44 | } as RequestOptions;
45 | })
46 | };
47 |
48 | // Create an instance of Single
49 | single = new Single(mockClient);
50 |
51 | // Setup a mock response
52 | mockResponse = {
53 | email: 'test@example.com',
54 | status: 'valid',
55 | result: 'valid',
56 | // Add other properties as needed for your tests
57 | };
58 |
59 | // Mock the request method directly on the Single instance
60 | single.request = jest.fn();
61 | single.request.mockResolvedValue(mockResponse);
62 | });
63 |
64 | describe('check method', () => {
65 |
66 | test('should include address_info when provided', async () => {
67 | const email = 'test@example.com';
68 | const address_info = true;
69 |
70 | await single.check(email, address_info);
71 |
72 | expect(single.request).toHaveBeenCalledWith(
73 | {
74 | method: 'GET',
75 | path: 'single/check'
76 | },
77 | {
78 | email: 'test@example.com',
79 | address_info: true,
80 | credits_info: undefined,
81 | timeout: undefined
82 | }
83 | );
84 | });
85 |
86 | test('should include credits_info when provided', async () => {
87 | const email = 'test@example.com';
88 | const address_info = false;
89 | const credits_info = true;
90 |
91 | await single.check(email, address_info, credits_info);
92 |
93 | expect(single.request).toHaveBeenCalledWith(
94 | {
95 | method: 'GET',
96 | path: 'single/check'
97 | },
98 | {
99 | email: 'test@example.com',
100 | address_info: undefined,
101 | credits_info: true,
102 | timeout: undefined
103 | }
104 | );
105 | });
106 |
107 | test('should include timeout when provided', async () => {
108 | const email = 'test@example.com';
109 | const address_info = false;
110 | const credits_info = false;
111 | const timeout = 5000;
112 |
113 | await single.check(email, address_info, credits_info, timeout);
114 |
115 | expect(single.request).toHaveBeenCalledWith(
116 | {
117 | method: 'GET',
118 | path: 'single/check'
119 | },
120 | {
121 | email: 'test@example.com',
122 | address_info: undefined,
123 | credits_info: undefined,
124 | timeout: 5000
125 | }
126 | );
127 | });
128 |
129 | test('should include historicalData when provided as true', async () => {
130 | const email = 'test@example.com';
131 | const address_info = false;
132 | const credits_info = false;
133 | const timeout = null;
134 | const historicalData = true;
135 |
136 | await single.check(email, address_info, credits_info, timeout, historicalData);
137 |
138 | expect(single.request).toHaveBeenCalledWith(
139 | {
140 | method: 'GET',
141 | path: 'single/check'
142 | },
143 | {
144 | email: 'test@example.com',
145 | address_info: undefined,
146 | credits_info: undefined,
147 | timeout: undefined,
148 | request_meta_data: {
149 | leverage_historical_data: 1
150 | }
151 | }
152 | );
153 | });
154 |
155 | test('should include historicalData when provided as false', async () => {
156 | const email = 'test@example.com';
157 | const address_info = false;
158 | const credits_info = false;
159 | const timeout = null;
160 | const historicalData = false;
161 |
162 | await single.check(email, address_info, credits_info, timeout, historicalData);
163 |
164 | expect(single.request).toHaveBeenCalledWith(
165 | {
166 | method: 'GET',
167 | path: 'single/check'
168 | },
169 | {
170 | email: 'test@example.com',
171 | address_info: undefined,
172 | credits_info: undefined,
173 | timeout: undefined,
174 | request_meta_data: {
175 | leverage_historical_data: 0
176 | }
177 | }
178 | );
179 | });
180 |
181 | test('should include all parameters when provided', async () => {
182 | const email = 'test@example.com';
183 | const address_info = true;
184 | const credits_info = true;
185 | const timeout = 10000;
186 | const historicalData = true;
187 |
188 | await single.check(email, address_info, credits_info, timeout, historicalData);
189 |
190 | expect(single.request).toHaveBeenCalledWith(
191 | {
192 | method: 'GET',
193 | path: 'single/check'
194 | },
195 | {
196 | email: 'test@example.com',
197 | address_info: true,
198 | credits_info: true,
199 | timeout: 10000,
200 | request_meta_data: {
201 | leverage_historical_data: 1
202 | }
203 | }
204 | );
205 | });
206 |
207 |
208 | });
209 | });
210 |
--------------------------------------------------------------------------------
/tests/VerificationObject.test.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import { describe, it, expect } from '@jest/globals';
4 | import VerificationObject from '../src/VerificationObject.js';
5 | import { SingleVerificationResponse } from '../src/types.js';
6 |
7 | describe('VerificationObject', () => {
8 | // Sample verification response
9 | const validResponse: SingleVerificationResponse = {
10 | status: 'success',
11 | result: 0,
12 | flags: ['has_dns', 'has_dns_mx', 'smtp_connectable'],
13 | execution_time: 123,
14 | address_info: {
15 | original: 'test@example.com',
16 | normalized: 'test@example.com',
17 | addr: 'test',
18 | alias: '',
19 | host: 'example.com',
20 | fqdn: 'example.com',
21 | domain: 'example',
22 | subdomain: '',
23 | tld: 'com'
24 | },
25 | credits_info: {
26 | paid_credits_used: 1,
27 | free_credits_used: 0,
28 | paid_credits_remaining: 999,
29 | free_credits_remaining: 0
30 | }
31 | };
32 |
33 | // Sample invalid response for testing
34 | const invalidResponse: SingleVerificationResponse = {
35 | status: 'success',
36 | result: 1,
37 | flags: ['bad_syntax'],
38 | execution_time: 123
39 | };
40 |
41 | // Use invalidResponse in a test to avoid unused variable warning
42 | it('should handle invalid responses', () => {
43 | const verificationObject = new VerificationObject(invalidResponse);
44 | expect(verificationObject.getResult()).toBe(1);
45 | expect(verificationObject.hasFlag('bad_syntax')).toBe(true);
46 | });
47 |
48 | describe('constructor', () => {
49 | it('should initialize with the provided response', () => {
50 | const verificationObject = new VerificationObject(validResponse);
51 | expect(verificationObject.getResponse()).toBe(validResponse);
52 | });
53 | });
54 |
55 | describe('getResult', () => {
56 | it('should return the result from the response', () => {
57 | const verificationObject = new VerificationObject(validResponse);
58 | expect(verificationObject.getResult()).toBe(0);
59 | });
60 | });
61 |
62 | describe('getNumericResult', () => {
63 | it('should return the numeric result for a text result', () => {
64 | // Create a response with a text result
65 | const textResponse = { ...validResponse, result: 'valid' as unknown as number };
66 | const verificationObject = new VerificationObject(textResponse);
67 |
68 | // Mock the static property access
69 | const originalValid = VerificationObject.valid;
70 | (VerificationObject as any).valid = 0;
71 |
72 | expect(verificationObject.getNumericResult()).toBe(0);
73 |
74 | // Restore the original value
75 | (VerificationObject as any).valid = originalValid;
76 | });
77 |
78 | it('should return the text result for a numeric result', () => {
79 | const verificationObject = new VerificationObject(validResponse);
80 |
81 | // Mock the static property access
82 | const originalHelper = VerificationObject.helpers;
83 | (VerificationObject as any)[0] = 'valid';
84 |
85 | expect(verificationObject.getNumericResult()).toBe('valid');
86 |
87 | // Restore the original value
88 | (VerificationObject as any).helpers = originalHelper;
89 | });
90 | });
91 |
92 | describe('is', () => {
93 | it('should return true if the result matches the specified code', () => {
94 | const verificationObject = new VerificationObject(validResponse);
95 | expect(verificationObject.is(0)).toBe(true);
96 | expect(verificationObject.is('valid')).toBe(true);
97 | });
98 |
99 | it('should return true if the result matches any of the specified codes', () => {
100 | const verificationObject = new VerificationObject(validResponse);
101 | expect(verificationObject.is([0, 1])).toBe(true);
102 | expect(verificationObject.is(['valid', 'invalid'])).toBe(true);
103 | });
104 |
105 | it('should return false if the result does not match the specified code', () => {
106 | const verificationObject = new VerificationObject(validResponse);
107 | expect(verificationObject.is(1)).toBe(false);
108 | expect(verificationObject.is('invalid')).toBe(false);
109 | });
110 |
111 | it('should return false if the result does not match any of the specified codes', () => {
112 | const verificationObject = new VerificationObject(validResponse);
113 | expect(verificationObject.is([1, 2])).toBe(false);
114 | expect(verificationObject.is(['invalid', 'disposable'])).toBe(false);
115 | });
116 | });
117 |
118 | describe('not', () => {
119 | it('should return false if the result matches the specified code', () => {
120 | const verificationObject = new VerificationObject(validResponse);
121 | expect(verificationObject.not(0)).toBe(false);
122 | expect(verificationObject.not('valid')).toBe(false);
123 | });
124 |
125 | it('should return false if the result matches any of the specified codes', () => {
126 | const verificationObject = new VerificationObject(validResponse);
127 | expect(verificationObject.not([0, 1])).toBe(false);
128 | expect(verificationObject.not(['valid', 'invalid'])).toBe(false);
129 | });
130 |
131 | it('should return true if the result does not match the specified code', () => {
132 | const verificationObject = new VerificationObject(validResponse);
133 | expect(verificationObject.not(1)).toBe(true);
134 | expect(verificationObject.not('invalid')).toBe(true);
135 | });
136 |
137 | it('should return true if the result does not match any of the specified codes', () => {
138 | const verificationObject = new VerificationObject(validResponse);
139 | expect(verificationObject.not([1, 2])).toBe(true);
140 | expect(verificationObject.not(['invalid', 'disposable'])).toBe(true);
141 | });
142 | });
143 |
144 | describe('hasFlag', () => {
145 | it('should return true if the flag is present', () => {
146 | const verificationObject = new VerificationObject(validResponse);
147 | expect(verificationObject.hasFlag('has_dns')).toBe(true);
148 | expect(verificationObject.hasFlag('has_dns_mx')).toBe(true);
149 | expect(verificationObject.hasFlag('smtp_connectable')).toBe(true);
150 | });
151 |
152 | it('should return false if the flag is not present', () => {
153 | const verificationObject = new VerificationObject(validResponse);
154 | expect(verificationObject.hasFlag('bad_syntax')).toBe(false);
155 | expect(verificationObject.hasFlag('disposable_email')).toBe(false);
156 | });
157 |
158 | it('should return false if the response has no flags', () => {
159 | const noFlagsResponse = { ...validResponse, flags: [] };
160 | const verificationObject = new VerificationObject(noFlagsResponse);
161 | expect(verificationObject.hasFlag('has_dns')).toBe(false);
162 | });
163 | });
164 |
165 | describe('static properties', () => {
166 | it('should expose result code constants', () => {
167 | expect(VerificationObject.valid).toBe(0);
168 | expect(VerificationObject.invalid).toBe(1);
169 | expect(VerificationObject.disposable).toBe(2);
170 | expect(VerificationObject.catchall).toBe(3);
171 | expect(VerificationObject.unknown).toBe(4);
172 | });
173 |
174 | it('should expose helpers object with result codes and flags', () => {
175 | expect(VerificationObject.helpers.valid).toBe(0);
176 | expect(VerificationObject.helpers.invalid).toBe(1);
177 | expect(VerificationObject.helpers.disposable).toBe(2);
178 | expect(VerificationObject.helpers.catchall).toBe(3);
179 | expect(VerificationObject.helpers.unknown).toBe(4);
180 |
181 | expect(VerificationObject.helpers[0]).toBe('valid');
182 | expect(VerificationObject.helpers[1]).toBe('invalid');
183 | expect(VerificationObject.helpers[2]).toBe('disposable');
184 | expect(VerificationObject.helpers[3]).toBe('catchall');
185 | expect(VerificationObject.helpers[4]).toBe('unknown');
186 |
187 | expect(VerificationObject.helpers.flags.has_dns).toBe('has_dns');
188 | expect(VerificationObject.helpers.flags.has_dns_mx).toBe('has_dns_mx');
189 | expect(VerificationObject.helpers.flags.bad_syntax).toBe('bad_syntax');
190 | // Check a few more flags
191 | expect(VerificationObject.helpers.flags.free_email_host).toBe('free_email_host');
192 | expect(VerificationObject.helpers.flags.disposable_email).toBe('disposable_email');
193 | });
194 |
195 | it('should expose deprecated numericCodes object', () => {
196 | expect(VerificationObject.numericCodes.valid).toBe(0);
197 | expect(VerificationObject.numericCodes.invalid).toBe(1);
198 | expect(VerificationObject.numericCodes.disposable).toBe(2);
199 | expect(VerificationObject.numericCodes.catchall).toBe(3);
200 | expect(VerificationObject.numericCodes.unknown).toBe(4);
201 | });
202 | });
203 | });
204 |
--------------------------------------------------------------------------------
/tests/setup.ts:
--------------------------------------------------------------------------------
1 | // This file is used to set up the Jest environment for TypeScript
2 | // It's referenced in the jest.config.js file
3 |
4 | // Make Jest functions globally available for ES modules
5 | import { jest } from '@jest/globals';
6 |
7 | // Explicitly make jest available globally
8 | global.jest = jest;
9 |
--------------------------------------------------------------------------------
/tests/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "types": ["jest", "node"],
5 | "esModuleInterop": true,
6 | "noImplicitAny": false,
7 | "strictNullChecks": false,
8 | "strict": false,
9 | "noEmit": true,
10 | "allowJs": true,
11 | "checkJs": false
12 | },
13 | "include": [
14 | "**/*.ts"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tsconfig.cjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "commonjs",
5 | "outDir": "./dist/cjs",
6 | "declaration": true
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2020",
4 | "module": "es2020",
5 | "moduleResolution": "node",
6 | "declaration": true,
7 | "outDir": "./dist/esm",
8 | "strict": true,
9 | "esModuleInterop": true,
10 | "skipLibCheck": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "typeRoots": ["./node_modules/@types", "./src/types"]
13 | },
14 | "include": ["src/**/*", "examples/**/*.ts"],
15 | "exclude": ["node_modules", "dist", "test"]
16 | }
17 |
--------------------------------------------------------------------------------