@mailtester-ninja/client 0.0.0-stage → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +97 -2
- package/dist/index.cjs +186 -0
- package/dist/index.d.cts +135 -0
- package/dist/index.d.ts +135 -0
- package/dist/index.js +155 -0
- package/package.json +66 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MailTester Ninja (Unlimited & Limited)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,98 @@
|
|
|
1
|
-
#
|
|
1
|
+
# MailTester Ninja for JavaScript and TypeScript
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Official client for the [MailTester Ninja](https://mailtester.ninja) email verification API. Check in real time whether an email address is valid and deliverable: each address is checked against the recipient's mail server, and nothing is stored. With a paid key, `find` also looks up a person's work email from their name and company domain.
|
|
4
|
+
|
|
5
|
+
- Zero dependencies, typed, ESM and CommonJS
|
|
6
|
+
- Node.js 18+, Deno, Bun and browsers (CORS is open)
|
|
7
|
+
- Free to try: no signup with `key: "free"`, or a [free API token](https://mailtester.ninja/free-email-verification-api/) for 50 checks a day
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @mailtester-ninja/client
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quickstart
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import { MailTester } from '@mailtester-ninja/client';
|
|
17
|
+
|
|
18
|
+
const mt = new MailTester({ key: process.env.MAILTESTER_KEY }); // falls back to "free"
|
|
19
|
+
|
|
20
|
+
const result = await mt.verify('info@apple.com');
|
|
21
|
+
console.log(result.code, result.message); // "ok" "Accepted"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
CommonJS works too: `const { MailTester } = require('@mailtester-ninja/client');`
|
|
25
|
+
|
|
26
|
+
## Reading the verdict
|
|
27
|
+
|
|
28
|
+
Branch on `code`, show `message` to people.
|
|
29
|
+
|
|
30
|
+
| `code` | Meaning | Typical `message` |
|
|
31
|
+
|--------|---------|-------------------|
|
|
32
|
+
| `ok` | Valid and deliverable | Accepted |
|
|
33
|
+
| `ko` | Invalid, will bounce | Rejected, No MX, Disposable, Limited |
|
|
34
|
+
| `mb` | Uncertain: catch-all domain, or the server could not be probed | Catch-All, MX Error, Timeout, SPAM Block, Greylisted |
|
|
35
|
+
|
|
36
|
+
A catch-all domain is never reported as valid.
|
|
37
|
+
|
|
38
|
+
`await mt.isDeliverable(email)` is a shortcut that returns `true` only for `ok`.
|
|
39
|
+
|
|
40
|
+
## Verify a list
|
|
41
|
+
|
|
42
|
+
`verifyMany` checks one address at a time and paces the calls for you: 30 seconds apart on the free tier, 1 second with a paid key (override with `delayMs`). Errors are reported per address, the loop keeps going.
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
for await (const { email, result, error } of mt.verifyMany(['info@apple.com', 'someone@example.com'])) {
|
|
46
|
+
if (error) console.log(email, 'no verdict:', error.message);
|
|
47
|
+
else console.log(email, result.code, result.message);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Find a work email (paid key)
|
|
52
|
+
|
|
53
|
+
`find` calls the Finder API: it tests the most common address formats one by one (first.last, flast, first, firstlast, first_last, f.last) and returns the first one the mail server accepts.
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
const r = await mt.find('Jean-Pierre', 'De La Tour', 'example.com');
|
|
57
|
+
if (r.found) console.log(r.email); // an address the mail server just accepted
|
|
58
|
+
else console.log(r.message, r.guess); // e.g. "Catch-All" and the most common format
|
|
59
|
+
console.log(r.permutations); // formats tested, each one counted as a verification
|
|
60
|
+
console.log(r.format, r.confidence); // e.g. "flast" "medium": an initial, a namesake may share it
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- Each format tested counts as one verification against your daily limit, even when nothing is found. The search paces itself within the rate of your key.
|
|
64
|
+
- `confidence` tells how sure the address is to belong to this person: `high` when it holds the first and last name, `medium` with an initial, `low` with the first name only. `tested` lists every address checked, with its verdict.
|
|
65
|
+
- On a catch-all domain nothing can be confirmed: `found` is `false` and `guess` holds the most common format.
|
|
66
|
+
- Personal email providers such as gmail.com are not searched (`message` is `Webmail`).
|
|
67
|
+
- A free key is refused with `MailTesterKeyError`; over the rate of your key, `MailTesterRateLimitError` carries `retryAfterSeconds`.
|
|
68
|
+
|
|
69
|
+
## Errors
|
|
70
|
+
|
|
71
|
+
An error is never a verdict: do not drop an address because of it.
|
|
72
|
+
|
|
73
|
+
| Error | When |
|
|
74
|
+
|-------|------|
|
|
75
|
+
| `MailTesterKeyError` | The key is invalid or disabled, or `find` was called with a free key. |
|
|
76
|
+
| `MailTesterRateLimitError` | A limit was reached (HTTP 429). `retryAfterSeconds` is set on the free tier and by `find`. |
|
|
77
|
+
| `MailTesterError` | Any other HTTP or network error (`status` when there is one). |
|
|
78
|
+
|
|
79
|
+
## Keys and limits
|
|
80
|
+
|
|
81
|
+
| Key | Daily checks | Pace |
|
|
82
|
+
|-----|--------------|------|
|
|
83
|
+
| `free` | 10 per IP | 1 request every 30 s |
|
|
84
|
+
| Free token `mtf_...` ([get one](https://mailtester.ninja/free-email-verification-api/)) | 50 per token | 1 request every 30 s |
|
|
85
|
+
| Paid key `sub_...` ([plans](https://mailtester.ninja/verify-emails-unlimited/)) | 100,000 to 200,000+ | daily limit / 8640 per 10 s (Pro 11, Ultimate 23) |
|
|
86
|
+
|
|
87
|
+
Options: `new MailTester({ key, baseUrl, timeoutMs, fetch })`. The key defaults to the `MAILTESTER_KEY` environment variable, then to `free`.
|
|
88
|
+
|
|
89
|
+
## Links
|
|
90
|
+
|
|
91
|
+
- [API documentation](https://mailtester.ninja/api/) and [OpenAPI 3.1 specification](https://mailtester.ninja/openapi.json)
|
|
92
|
+
- [Integrations](https://mailtester.ninja/integrations/): n8n, Zapier, Make, Clay, Google Sheets
|
|
93
|
+
- [Code examples](https://github.com/mailtester-ninja/mailtester-api-examples) for other languages
|
|
94
|
+
- [Python client](https://pypi.org/project/mailtester-ninja/)
|
|
95
|
+
|
|
96
|
+
## License
|
|
97
|
+
|
|
98
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
|
|
24
|
+
FREE_KEY: () => FREE_KEY,
|
|
25
|
+
MailTester: () => MailTester,
|
|
26
|
+
MailTesterError: () => MailTesterError,
|
|
27
|
+
MailTesterKeyError: () => MailTesterKeyError,
|
|
28
|
+
MailTesterRateLimitError: () => MailTesterRateLimitError,
|
|
29
|
+
VERSION: () => VERSION,
|
|
30
|
+
default: () => index_default
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(index_exports);
|
|
33
|
+
var VERSION = "1.1.0";
|
|
34
|
+
var DEFAULT_BASE_URL = "https://happy.mailtester.ninja";
|
|
35
|
+
var FREE_KEY = "free";
|
|
36
|
+
var MailTesterError = class extends Error {
|
|
37
|
+
status;
|
|
38
|
+
constructor(message, status) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.name = "MailTesterError";
|
|
41
|
+
this.status = status;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var MailTesterKeyError = class extends MailTesterError {
|
|
45
|
+
constructor(message, status) {
|
|
46
|
+
super(message, status);
|
|
47
|
+
this.name = "MailTesterKeyError";
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
var MailTesterRateLimitError = class extends MailTesterError {
|
|
51
|
+
retryAfterSeconds;
|
|
52
|
+
constructor(message, retryAfterSeconds) {
|
|
53
|
+
super(message, 429);
|
|
54
|
+
this.name = "MailTesterRateLimitError";
|
|
55
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
59
|
+
function envKey() {
|
|
60
|
+
const g = globalThis;
|
|
61
|
+
return g.process?.env?.MAILTESTER_KEY || void 0;
|
|
62
|
+
}
|
|
63
|
+
function parseJson(text) {
|
|
64
|
+
try {
|
|
65
|
+
const value = JSON.parse(text);
|
|
66
|
+
return value && typeof value === "object" ? value : void 0;
|
|
67
|
+
} catch {
|
|
68
|
+
return void 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function retryAfter(message) {
|
|
72
|
+
const m = /retry in (\d+)\s*s/i.exec(message);
|
|
73
|
+
return m ? Number(m[1]) : void 0;
|
|
74
|
+
}
|
|
75
|
+
var MailTester = class {
|
|
76
|
+
key;
|
|
77
|
+
baseUrl;
|
|
78
|
+
timeoutMs;
|
|
79
|
+
fetchImpl;
|
|
80
|
+
constructor(options = {}) {
|
|
81
|
+
const opts = typeof options === "string" ? { key: options } : options;
|
|
82
|
+
this.key = (opts.key ?? envKey() ?? FREE_KEY).trim().replace(/^\{|\}$/g, "");
|
|
83
|
+
this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
84
|
+
this.timeoutMs = opts.timeoutMs ?? 6e4;
|
|
85
|
+
const f = opts.fetch ?? globalThis.fetch;
|
|
86
|
+
if (!f) throw new MailTesterError("No fetch implementation available: use Node.js 18 or later, or pass options.fetch");
|
|
87
|
+
this.fetchImpl = f;
|
|
88
|
+
}
|
|
89
|
+
/** True on the free tier (`free` or a free token), limited to one request every 30 seconds. */
|
|
90
|
+
get isFree() {
|
|
91
|
+
return this.key === FREE_KEY || this.key.startsWith("mtf_");
|
|
92
|
+
}
|
|
93
|
+
async request(url) {
|
|
94
|
+
const controller = new AbortController();
|
|
95
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
96
|
+
let res;
|
|
97
|
+
try {
|
|
98
|
+
res = await this.fetchImpl(url, { headers: { Accept: "application/json" }, signal: controller.signal });
|
|
99
|
+
} catch (err) {
|
|
100
|
+
const aborted = err?.name === "AbortError";
|
|
101
|
+
throw new MailTesterError(aborted ? `Request timed out after ${this.timeoutMs} ms` : `Network error: ${String(err?.message ?? err)}`);
|
|
102
|
+
} finally {
|
|
103
|
+
clearTimeout(timer);
|
|
104
|
+
}
|
|
105
|
+
const text = await res.text();
|
|
106
|
+
return { res, text, body: parseJson(text) };
|
|
107
|
+
}
|
|
108
|
+
/** Verify one address. Resolves to a verdict, rejects with a MailTesterError when there is none. */
|
|
109
|
+
async verify(email) {
|
|
110
|
+
const { res, text, body } = await this.request(`${this.baseUrl}/ninja?email=${encodeURIComponent(email.trim())}&key=${encodeURIComponent(this.key)}`);
|
|
111
|
+
const message = typeof body?.message === "string" ? body.message : text.trim();
|
|
112
|
+
if (res.status === 429) throw new MailTesterRateLimitError(message, retryAfter(message));
|
|
113
|
+
if (!res.ok) throw new MailTesterError(message || `HTTP ${res.status}`, res.status);
|
|
114
|
+
if (!body) throw new MailTesterError("Unexpected response from the API", res.status);
|
|
115
|
+
if (body.code === "--") throw new MailTesterKeyError(message, res.status);
|
|
116
|
+
return body;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Find a work email address from a first name, a last name and a company domain (Finder API, paid key).
|
|
120
|
+
* The API tests the most common formats one by one (first.last, flast, first, firstlast, first_last, f.last)
|
|
121
|
+
* and returns the first address the mail server accepts. Each format tested counts as one verification.
|
|
122
|
+
* Resolves to a FindResult (check `found`), rejects with a MailTesterError when the search did not run.
|
|
123
|
+
*/
|
|
124
|
+
async find(first, last, domain) {
|
|
125
|
+
if (this.isFree) {
|
|
126
|
+
throw new MailTesterKeyError("The Finder API needs a paid key: https://mailtester.ninja/verify-emails-unlimited/", 403);
|
|
127
|
+
}
|
|
128
|
+
const qs = new URLSearchParams({ first: first.trim(), last: last.trim(), domain: domain.trim(), key: this.key });
|
|
129
|
+
const { res, text, body } = await this.request(`${this.baseUrl}/find?${qs}`);
|
|
130
|
+
const reason = typeof body?.reason === "string" && body.reason ? body.reason : void 0;
|
|
131
|
+
const message = reason ?? (typeof body?.message === "string" ? body.message : text.trim());
|
|
132
|
+
if (res.status === 429) {
|
|
133
|
+
const wait = Number(res.headers.get("retry-after"));
|
|
134
|
+
throw new MailTesterRateLimitError(message, Number.isFinite(wait) && wait > 0 ? wait : void 0);
|
|
135
|
+
}
|
|
136
|
+
if (res.status === 403 && body) throw new MailTesterKeyError(message, 403);
|
|
137
|
+
if (!res.ok) throw new MailTesterError(message || `HTTP ${res.status}`, res.status);
|
|
138
|
+
if (!body) throw new MailTesterError("Unexpected response from the API", res.status);
|
|
139
|
+
if (body.code === "--") throw new MailTesterKeyError(message, res.status);
|
|
140
|
+
return body;
|
|
141
|
+
}
|
|
142
|
+
/** True only when the address is clearly valid and deliverable (`code` "ok"). */
|
|
143
|
+
async isDeliverable(email) {
|
|
144
|
+
return (await this.verify(email)).code === "ok";
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Verify a list, one address at a time, pacing the calls to respect the rate limit.
|
|
148
|
+
* Yields `{ email, result }` or `{ email, error }` for each non-empty address.
|
|
149
|
+
*/
|
|
150
|
+
async *verifyMany(emails, options = {}) {
|
|
151
|
+
const delayMs = options.delayMs ?? (this.isFree ? 3e4 : 1e3);
|
|
152
|
+
const retryOnLimit = options.retryOnLimit ?? true;
|
|
153
|
+
let first = true;
|
|
154
|
+
for await (const raw of emails) {
|
|
155
|
+
const email = String(raw).trim();
|
|
156
|
+
if (!email) continue;
|
|
157
|
+
if (!first && delayMs > 0) await sleep(delayMs);
|
|
158
|
+
first = false;
|
|
159
|
+
try {
|
|
160
|
+
yield { email, result: await this.verify(email) };
|
|
161
|
+
} catch (err) {
|
|
162
|
+
if (retryOnLimit && err instanceof MailTesterRateLimitError && err.retryAfterSeconds !== void 0) {
|
|
163
|
+
await sleep((err.retryAfterSeconds + 1) * 1e3);
|
|
164
|
+
try {
|
|
165
|
+
yield { email, result: await this.verify(email) };
|
|
166
|
+
} catch (err2) {
|
|
167
|
+
yield { email, error: err2 instanceof MailTesterError ? err2 : new MailTesterError(String(err2)) };
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
yield { email, error: err instanceof MailTesterError ? err : new MailTesterError(String(err)) };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
var index_default = MailTester;
|
|
177
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
178
|
+
0 && (module.exports = {
|
|
179
|
+
DEFAULT_BASE_URL,
|
|
180
|
+
FREE_KEY,
|
|
181
|
+
MailTester,
|
|
182
|
+
MailTesterError,
|
|
183
|
+
MailTesterKeyError,
|
|
184
|
+
MailTesterRateLimitError,
|
|
185
|
+
VERSION
|
|
186
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Official JavaScript and TypeScript client for the MailTester Ninja email verification API.
|
|
3
|
+
* https://mailtester.ninja/api/
|
|
4
|
+
*/
|
|
5
|
+
declare const VERSION = "1.1.0";
|
|
6
|
+
declare const DEFAULT_BASE_URL = "https://happy.mailtester.ninja";
|
|
7
|
+
/** Keyless free tier: 10 checks a day per IP, one request every 30 seconds. */
|
|
8
|
+
declare const FREE_KEY = "free";
|
|
9
|
+
/** `ok` valid and deliverable, `ko` invalid, `mb` uncertain (catch-all or not probeable). */
|
|
10
|
+
type Verdict = 'ok' | 'ko' | 'mb';
|
|
11
|
+
interface VerificationResult {
|
|
12
|
+
/** Machine-readable verdict. */
|
|
13
|
+
code: Verdict;
|
|
14
|
+
/** Human-readable label: Accepted, Rejected, Catch-All, No MX, Disposable, Timeout... */
|
|
15
|
+
message: string;
|
|
16
|
+
/** The address that was checked. */
|
|
17
|
+
email: string;
|
|
18
|
+
/** A human-readable guess of the name, derived from the address. */
|
|
19
|
+
user?: string;
|
|
20
|
+
domain?: string | null;
|
|
21
|
+
/** Mail server used for the check. */
|
|
22
|
+
mx?: string;
|
|
23
|
+
/** Simultaneous connections used by the key. */
|
|
24
|
+
connections?: number;
|
|
25
|
+
/** Daily limit of the key. */
|
|
26
|
+
limit?: number;
|
|
27
|
+
/** Requests made with the key over the last 10 seconds. */
|
|
28
|
+
rate?: number;
|
|
29
|
+
}
|
|
30
|
+
/** Result of a Finder API search (`find`). Check `found` first. */
|
|
31
|
+
interface FindResult {
|
|
32
|
+
/** `ok` found, `ko` not found, `mb` uncertain (catch-all domain, personal email provider, server not checked). */
|
|
33
|
+
code: Verdict;
|
|
34
|
+
/** Accepted, Rejected, Catch-All, Limited, No MX, Disposable, SPAM Block, Timeout, Webmail... */
|
|
35
|
+
message: string;
|
|
36
|
+
/** True when the mail server accepted one of the formats. */
|
|
37
|
+
found: boolean;
|
|
38
|
+
/** The address found, otherwise null. */
|
|
39
|
+
email: string | null;
|
|
40
|
+
/** Pattern of the returned address (email, or guess when not found): first.last, flast, first, firstlast, first_last or f.last. */
|
|
41
|
+
format: string | null;
|
|
42
|
+
/** For an address found: `high` (first and last name in it), `medium` (an initial, a namesake may share it), `low` (first name only). */
|
|
43
|
+
confidence: 'high' | 'medium' | 'low' | null;
|
|
44
|
+
/** Not verified: on a catch-all domain the most common format, for a full mailbox (Limited) that address. */
|
|
45
|
+
guess: string | null;
|
|
46
|
+
first: string;
|
|
47
|
+
last: string;
|
|
48
|
+
/** The normalised domain that was searched. */
|
|
49
|
+
domain: string;
|
|
50
|
+
/** Mail server used for the search. */
|
|
51
|
+
mx: string;
|
|
52
|
+
/** Address formats tested, each one counted as a verification, even when nothing is found. */
|
|
53
|
+
permutations: number;
|
|
54
|
+
/** Every address tested, in order, with its verdict. */
|
|
55
|
+
tested: {
|
|
56
|
+
email: string;
|
|
57
|
+
code: string;
|
|
58
|
+
message: string;
|
|
59
|
+
}[];
|
|
60
|
+
/** Simultaneous connections used by the key. */
|
|
61
|
+
connections?: number;
|
|
62
|
+
/** Daily limit of the key. */
|
|
63
|
+
limit?: number;
|
|
64
|
+
/** Verifications made with the key over the last 10 seconds, permutations included. */
|
|
65
|
+
rate?: number;
|
|
66
|
+
/** Seconds taken by the search. */
|
|
67
|
+
duration?: number;
|
|
68
|
+
/** Why nothing was found, in plain English. Null when found. */
|
|
69
|
+
reason: string | null;
|
|
70
|
+
}
|
|
71
|
+
interface MailTesterOptions {
|
|
72
|
+
/**
|
|
73
|
+
* `free` (no signup), a free token `mtf_...` from https://mailtester.ninja/free-email-verification-api/,
|
|
74
|
+
* or a subscription key `sub_...`. Defaults to the MAILTESTER_KEY environment variable, then `free`.
|
|
75
|
+
*/
|
|
76
|
+
key?: string;
|
|
77
|
+
baseUrl?: string;
|
|
78
|
+
/** Request timeout in milliseconds. Default 60000. */
|
|
79
|
+
timeoutMs?: number;
|
|
80
|
+
/** Custom fetch implementation (tests, proxies). Defaults to the global fetch. */
|
|
81
|
+
fetch?: typeof fetch;
|
|
82
|
+
}
|
|
83
|
+
interface VerifyManyOptions {
|
|
84
|
+
/** Pause between calls in milliseconds. Default: 30000 on the free tier, 1000 with a paid key. */
|
|
85
|
+
delayMs?: number;
|
|
86
|
+
/** On a free tier limit (HTTP 429), wait the announced time and retry once. Default true. */
|
|
87
|
+
retryOnLimit?: boolean;
|
|
88
|
+
}
|
|
89
|
+
interface VerifyManyItem {
|
|
90
|
+
email: string;
|
|
91
|
+
result?: VerificationResult;
|
|
92
|
+
error?: MailTesterError;
|
|
93
|
+
}
|
|
94
|
+
/** Any error returned by the API or the network. Never a verdict: do not drop the address on it. */
|
|
95
|
+
declare class MailTesterError extends Error {
|
|
96
|
+
readonly status?: number;
|
|
97
|
+
constructor(message: string, status?: number);
|
|
98
|
+
}
|
|
99
|
+
/** The key is invalid or disabled (`code` "--"). */
|
|
100
|
+
declare class MailTesterKeyError extends MailTesterError {
|
|
101
|
+
constructor(message: string, status?: number);
|
|
102
|
+
}
|
|
103
|
+
/** A limit was reached (HTTP 429). `retryAfterSeconds` is set when the API says how long to wait. */
|
|
104
|
+
declare class MailTesterRateLimitError extends MailTesterError {
|
|
105
|
+
readonly retryAfterSeconds?: number;
|
|
106
|
+
constructor(message: string, retryAfterSeconds?: number);
|
|
107
|
+
}
|
|
108
|
+
declare class MailTester {
|
|
109
|
+
readonly key: string;
|
|
110
|
+
readonly baseUrl: string;
|
|
111
|
+
readonly timeoutMs: number;
|
|
112
|
+
private readonly fetchImpl;
|
|
113
|
+
constructor(options?: MailTesterOptions | string);
|
|
114
|
+
/** True on the free tier (`free` or a free token), limited to one request every 30 seconds. */
|
|
115
|
+
get isFree(): boolean;
|
|
116
|
+
private request;
|
|
117
|
+
/** Verify one address. Resolves to a verdict, rejects with a MailTesterError when there is none. */
|
|
118
|
+
verify(email: string): Promise<VerificationResult>;
|
|
119
|
+
/**
|
|
120
|
+
* Find a work email address from a first name, a last name and a company domain (Finder API, paid key).
|
|
121
|
+
* The API tests the most common formats one by one (first.last, flast, first, firstlast, first_last, f.last)
|
|
122
|
+
* and returns the first address the mail server accepts. Each format tested counts as one verification.
|
|
123
|
+
* Resolves to a FindResult (check `found`), rejects with a MailTesterError when the search did not run.
|
|
124
|
+
*/
|
|
125
|
+
find(first: string, last: string, domain: string): Promise<FindResult>;
|
|
126
|
+
/** True only when the address is clearly valid and deliverable (`code` "ok"). */
|
|
127
|
+
isDeliverable(email: string): Promise<boolean>;
|
|
128
|
+
/**
|
|
129
|
+
* Verify a list, one address at a time, pacing the calls to respect the rate limit.
|
|
130
|
+
* Yields `{ email, result }` or `{ email, error }` for each non-empty address.
|
|
131
|
+
*/
|
|
132
|
+
verifyMany(emails: Iterable<string> | AsyncIterable<string>, options?: VerifyManyOptions): AsyncGenerator<VerifyManyItem>;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export { DEFAULT_BASE_URL, FREE_KEY, type FindResult, MailTester, MailTesterError, MailTesterKeyError, type MailTesterOptions, MailTesterRateLimitError, VERSION, type Verdict, type VerificationResult, type VerifyManyItem, type VerifyManyOptions, MailTester as default };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Official JavaScript and TypeScript client for the MailTester Ninja email verification API.
|
|
3
|
+
* https://mailtester.ninja/api/
|
|
4
|
+
*/
|
|
5
|
+
declare const VERSION = "1.1.0";
|
|
6
|
+
declare const DEFAULT_BASE_URL = "https://happy.mailtester.ninja";
|
|
7
|
+
/** Keyless free tier: 10 checks a day per IP, one request every 30 seconds. */
|
|
8
|
+
declare const FREE_KEY = "free";
|
|
9
|
+
/** `ok` valid and deliverable, `ko` invalid, `mb` uncertain (catch-all or not probeable). */
|
|
10
|
+
type Verdict = 'ok' | 'ko' | 'mb';
|
|
11
|
+
interface VerificationResult {
|
|
12
|
+
/** Machine-readable verdict. */
|
|
13
|
+
code: Verdict;
|
|
14
|
+
/** Human-readable label: Accepted, Rejected, Catch-All, No MX, Disposable, Timeout... */
|
|
15
|
+
message: string;
|
|
16
|
+
/** The address that was checked. */
|
|
17
|
+
email: string;
|
|
18
|
+
/** A human-readable guess of the name, derived from the address. */
|
|
19
|
+
user?: string;
|
|
20
|
+
domain?: string | null;
|
|
21
|
+
/** Mail server used for the check. */
|
|
22
|
+
mx?: string;
|
|
23
|
+
/** Simultaneous connections used by the key. */
|
|
24
|
+
connections?: number;
|
|
25
|
+
/** Daily limit of the key. */
|
|
26
|
+
limit?: number;
|
|
27
|
+
/** Requests made with the key over the last 10 seconds. */
|
|
28
|
+
rate?: number;
|
|
29
|
+
}
|
|
30
|
+
/** Result of a Finder API search (`find`). Check `found` first. */
|
|
31
|
+
interface FindResult {
|
|
32
|
+
/** `ok` found, `ko` not found, `mb` uncertain (catch-all domain, personal email provider, server not checked). */
|
|
33
|
+
code: Verdict;
|
|
34
|
+
/** Accepted, Rejected, Catch-All, Limited, No MX, Disposable, SPAM Block, Timeout, Webmail... */
|
|
35
|
+
message: string;
|
|
36
|
+
/** True when the mail server accepted one of the formats. */
|
|
37
|
+
found: boolean;
|
|
38
|
+
/** The address found, otherwise null. */
|
|
39
|
+
email: string | null;
|
|
40
|
+
/** Pattern of the returned address (email, or guess when not found): first.last, flast, first, firstlast, first_last or f.last. */
|
|
41
|
+
format: string | null;
|
|
42
|
+
/** For an address found: `high` (first and last name in it), `medium` (an initial, a namesake may share it), `low` (first name only). */
|
|
43
|
+
confidence: 'high' | 'medium' | 'low' | null;
|
|
44
|
+
/** Not verified: on a catch-all domain the most common format, for a full mailbox (Limited) that address. */
|
|
45
|
+
guess: string | null;
|
|
46
|
+
first: string;
|
|
47
|
+
last: string;
|
|
48
|
+
/** The normalised domain that was searched. */
|
|
49
|
+
domain: string;
|
|
50
|
+
/** Mail server used for the search. */
|
|
51
|
+
mx: string;
|
|
52
|
+
/** Address formats tested, each one counted as a verification, even when nothing is found. */
|
|
53
|
+
permutations: number;
|
|
54
|
+
/** Every address tested, in order, with its verdict. */
|
|
55
|
+
tested: {
|
|
56
|
+
email: string;
|
|
57
|
+
code: string;
|
|
58
|
+
message: string;
|
|
59
|
+
}[];
|
|
60
|
+
/** Simultaneous connections used by the key. */
|
|
61
|
+
connections?: number;
|
|
62
|
+
/** Daily limit of the key. */
|
|
63
|
+
limit?: number;
|
|
64
|
+
/** Verifications made with the key over the last 10 seconds, permutations included. */
|
|
65
|
+
rate?: number;
|
|
66
|
+
/** Seconds taken by the search. */
|
|
67
|
+
duration?: number;
|
|
68
|
+
/** Why nothing was found, in plain English. Null when found. */
|
|
69
|
+
reason: string | null;
|
|
70
|
+
}
|
|
71
|
+
interface MailTesterOptions {
|
|
72
|
+
/**
|
|
73
|
+
* `free` (no signup), a free token `mtf_...` from https://mailtester.ninja/free-email-verification-api/,
|
|
74
|
+
* or a subscription key `sub_...`. Defaults to the MAILTESTER_KEY environment variable, then `free`.
|
|
75
|
+
*/
|
|
76
|
+
key?: string;
|
|
77
|
+
baseUrl?: string;
|
|
78
|
+
/** Request timeout in milliseconds. Default 60000. */
|
|
79
|
+
timeoutMs?: number;
|
|
80
|
+
/** Custom fetch implementation (tests, proxies). Defaults to the global fetch. */
|
|
81
|
+
fetch?: typeof fetch;
|
|
82
|
+
}
|
|
83
|
+
interface VerifyManyOptions {
|
|
84
|
+
/** Pause between calls in milliseconds. Default: 30000 on the free tier, 1000 with a paid key. */
|
|
85
|
+
delayMs?: number;
|
|
86
|
+
/** On a free tier limit (HTTP 429), wait the announced time and retry once. Default true. */
|
|
87
|
+
retryOnLimit?: boolean;
|
|
88
|
+
}
|
|
89
|
+
interface VerifyManyItem {
|
|
90
|
+
email: string;
|
|
91
|
+
result?: VerificationResult;
|
|
92
|
+
error?: MailTesterError;
|
|
93
|
+
}
|
|
94
|
+
/** Any error returned by the API or the network. Never a verdict: do not drop the address on it. */
|
|
95
|
+
declare class MailTesterError extends Error {
|
|
96
|
+
readonly status?: number;
|
|
97
|
+
constructor(message: string, status?: number);
|
|
98
|
+
}
|
|
99
|
+
/** The key is invalid or disabled (`code` "--"). */
|
|
100
|
+
declare class MailTesterKeyError extends MailTesterError {
|
|
101
|
+
constructor(message: string, status?: number);
|
|
102
|
+
}
|
|
103
|
+
/** A limit was reached (HTTP 429). `retryAfterSeconds` is set when the API says how long to wait. */
|
|
104
|
+
declare class MailTesterRateLimitError extends MailTesterError {
|
|
105
|
+
readonly retryAfterSeconds?: number;
|
|
106
|
+
constructor(message: string, retryAfterSeconds?: number);
|
|
107
|
+
}
|
|
108
|
+
declare class MailTester {
|
|
109
|
+
readonly key: string;
|
|
110
|
+
readonly baseUrl: string;
|
|
111
|
+
readonly timeoutMs: number;
|
|
112
|
+
private readonly fetchImpl;
|
|
113
|
+
constructor(options?: MailTesterOptions | string);
|
|
114
|
+
/** True on the free tier (`free` or a free token), limited to one request every 30 seconds. */
|
|
115
|
+
get isFree(): boolean;
|
|
116
|
+
private request;
|
|
117
|
+
/** Verify one address. Resolves to a verdict, rejects with a MailTesterError when there is none. */
|
|
118
|
+
verify(email: string): Promise<VerificationResult>;
|
|
119
|
+
/**
|
|
120
|
+
* Find a work email address from a first name, a last name and a company domain (Finder API, paid key).
|
|
121
|
+
* The API tests the most common formats one by one (first.last, flast, first, firstlast, first_last, f.last)
|
|
122
|
+
* and returns the first address the mail server accepts. Each format tested counts as one verification.
|
|
123
|
+
* Resolves to a FindResult (check `found`), rejects with a MailTesterError when the search did not run.
|
|
124
|
+
*/
|
|
125
|
+
find(first: string, last: string, domain: string): Promise<FindResult>;
|
|
126
|
+
/** True only when the address is clearly valid and deliverable (`code` "ok"). */
|
|
127
|
+
isDeliverable(email: string): Promise<boolean>;
|
|
128
|
+
/**
|
|
129
|
+
* Verify a list, one address at a time, pacing the calls to respect the rate limit.
|
|
130
|
+
* Yields `{ email, result }` or `{ email, error }` for each non-empty address.
|
|
131
|
+
*/
|
|
132
|
+
verifyMany(emails: Iterable<string> | AsyncIterable<string>, options?: VerifyManyOptions): AsyncGenerator<VerifyManyItem>;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export { DEFAULT_BASE_URL, FREE_KEY, type FindResult, MailTester, MailTesterError, MailTesterKeyError, type MailTesterOptions, MailTesterRateLimitError, VERSION, type Verdict, type VerificationResult, type VerifyManyItem, type VerifyManyOptions, MailTester as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var VERSION = "1.1.0";
|
|
3
|
+
var DEFAULT_BASE_URL = "https://happy.mailtester.ninja";
|
|
4
|
+
var FREE_KEY = "free";
|
|
5
|
+
var MailTesterError = class extends Error {
|
|
6
|
+
status;
|
|
7
|
+
constructor(message, status) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "MailTesterError";
|
|
10
|
+
this.status = status;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var MailTesterKeyError = class extends MailTesterError {
|
|
14
|
+
constructor(message, status) {
|
|
15
|
+
super(message, status);
|
|
16
|
+
this.name = "MailTesterKeyError";
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var MailTesterRateLimitError = class extends MailTesterError {
|
|
20
|
+
retryAfterSeconds;
|
|
21
|
+
constructor(message, retryAfterSeconds) {
|
|
22
|
+
super(message, 429);
|
|
23
|
+
this.name = "MailTesterRateLimitError";
|
|
24
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
28
|
+
function envKey() {
|
|
29
|
+
const g = globalThis;
|
|
30
|
+
return g.process?.env?.MAILTESTER_KEY || void 0;
|
|
31
|
+
}
|
|
32
|
+
function parseJson(text) {
|
|
33
|
+
try {
|
|
34
|
+
const value = JSON.parse(text);
|
|
35
|
+
return value && typeof value === "object" ? value : void 0;
|
|
36
|
+
} catch {
|
|
37
|
+
return void 0;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function retryAfter(message) {
|
|
41
|
+
const m = /retry in (\d+)\s*s/i.exec(message);
|
|
42
|
+
return m ? Number(m[1]) : void 0;
|
|
43
|
+
}
|
|
44
|
+
var MailTester = class {
|
|
45
|
+
key;
|
|
46
|
+
baseUrl;
|
|
47
|
+
timeoutMs;
|
|
48
|
+
fetchImpl;
|
|
49
|
+
constructor(options = {}) {
|
|
50
|
+
const opts = typeof options === "string" ? { key: options } : options;
|
|
51
|
+
this.key = (opts.key ?? envKey() ?? FREE_KEY).trim().replace(/^\{|\}$/g, "");
|
|
52
|
+
this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
53
|
+
this.timeoutMs = opts.timeoutMs ?? 6e4;
|
|
54
|
+
const f = opts.fetch ?? globalThis.fetch;
|
|
55
|
+
if (!f) throw new MailTesterError("No fetch implementation available: use Node.js 18 or later, or pass options.fetch");
|
|
56
|
+
this.fetchImpl = f;
|
|
57
|
+
}
|
|
58
|
+
/** True on the free tier (`free` or a free token), limited to one request every 30 seconds. */
|
|
59
|
+
get isFree() {
|
|
60
|
+
return this.key === FREE_KEY || this.key.startsWith("mtf_");
|
|
61
|
+
}
|
|
62
|
+
async request(url) {
|
|
63
|
+
const controller = new AbortController();
|
|
64
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
65
|
+
let res;
|
|
66
|
+
try {
|
|
67
|
+
res = await this.fetchImpl(url, { headers: { Accept: "application/json" }, signal: controller.signal });
|
|
68
|
+
} catch (err) {
|
|
69
|
+
const aborted = err?.name === "AbortError";
|
|
70
|
+
throw new MailTesterError(aborted ? `Request timed out after ${this.timeoutMs} ms` : `Network error: ${String(err?.message ?? err)}`);
|
|
71
|
+
} finally {
|
|
72
|
+
clearTimeout(timer);
|
|
73
|
+
}
|
|
74
|
+
const text = await res.text();
|
|
75
|
+
return { res, text, body: parseJson(text) };
|
|
76
|
+
}
|
|
77
|
+
/** Verify one address. Resolves to a verdict, rejects with a MailTesterError when there is none. */
|
|
78
|
+
async verify(email) {
|
|
79
|
+
const { res, text, body } = await this.request(`${this.baseUrl}/ninja?email=${encodeURIComponent(email.trim())}&key=${encodeURIComponent(this.key)}`);
|
|
80
|
+
const message = typeof body?.message === "string" ? body.message : text.trim();
|
|
81
|
+
if (res.status === 429) throw new MailTesterRateLimitError(message, retryAfter(message));
|
|
82
|
+
if (!res.ok) throw new MailTesterError(message || `HTTP ${res.status}`, res.status);
|
|
83
|
+
if (!body) throw new MailTesterError("Unexpected response from the API", res.status);
|
|
84
|
+
if (body.code === "--") throw new MailTesterKeyError(message, res.status);
|
|
85
|
+
return body;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Find a work email address from a first name, a last name and a company domain (Finder API, paid key).
|
|
89
|
+
* The API tests the most common formats one by one (first.last, flast, first, firstlast, first_last, f.last)
|
|
90
|
+
* and returns the first address the mail server accepts. Each format tested counts as one verification.
|
|
91
|
+
* Resolves to a FindResult (check `found`), rejects with a MailTesterError when the search did not run.
|
|
92
|
+
*/
|
|
93
|
+
async find(first, last, domain) {
|
|
94
|
+
if (this.isFree) {
|
|
95
|
+
throw new MailTesterKeyError("The Finder API needs a paid key: https://mailtester.ninja/verify-emails-unlimited/", 403);
|
|
96
|
+
}
|
|
97
|
+
const qs = new URLSearchParams({ first: first.trim(), last: last.trim(), domain: domain.trim(), key: this.key });
|
|
98
|
+
const { res, text, body } = await this.request(`${this.baseUrl}/find?${qs}`);
|
|
99
|
+
const reason = typeof body?.reason === "string" && body.reason ? body.reason : void 0;
|
|
100
|
+
const message = reason ?? (typeof body?.message === "string" ? body.message : text.trim());
|
|
101
|
+
if (res.status === 429) {
|
|
102
|
+
const wait = Number(res.headers.get("retry-after"));
|
|
103
|
+
throw new MailTesterRateLimitError(message, Number.isFinite(wait) && wait > 0 ? wait : void 0);
|
|
104
|
+
}
|
|
105
|
+
if (res.status === 403 && body) throw new MailTesterKeyError(message, 403);
|
|
106
|
+
if (!res.ok) throw new MailTesterError(message || `HTTP ${res.status}`, res.status);
|
|
107
|
+
if (!body) throw new MailTesterError("Unexpected response from the API", res.status);
|
|
108
|
+
if (body.code === "--") throw new MailTesterKeyError(message, res.status);
|
|
109
|
+
return body;
|
|
110
|
+
}
|
|
111
|
+
/** True only when the address is clearly valid and deliverable (`code` "ok"). */
|
|
112
|
+
async isDeliverable(email) {
|
|
113
|
+
return (await this.verify(email)).code === "ok";
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Verify a list, one address at a time, pacing the calls to respect the rate limit.
|
|
117
|
+
* Yields `{ email, result }` or `{ email, error }` for each non-empty address.
|
|
118
|
+
*/
|
|
119
|
+
async *verifyMany(emails, options = {}) {
|
|
120
|
+
const delayMs = options.delayMs ?? (this.isFree ? 3e4 : 1e3);
|
|
121
|
+
const retryOnLimit = options.retryOnLimit ?? true;
|
|
122
|
+
let first = true;
|
|
123
|
+
for await (const raw of emails) {
|
|
124
|
+
const email = String(raw).trim();
|
|
125
|
+
if (!email) continue;
|
|
126
|
+
if (!first && delayMs > 0) await sleep(delayMs);
|
|
127
|
+
first = false;
|
|
128
|
+
try {
|
|
129
|
+
yield { email, result: await this.verify(email) };
|
|
130
|
+
} catch (err) {
|
|
131
|
+
if (retryOnLimit && err instanceof MailTesterRateLimitError && err.retryAfterSeconds !== void 0) {
|
|
132
|
+
await sleep((err.retryAfterSeconds + 1) * 1e3);
|
|
133
|
+
try {
|
|
134
|
+
yield { email, result: await this.verify(email) };
|
|
135
|
+
} catch (err2) {
|
|
136
|
+
yield { email, error: err2 instanceof MailTesterError ? err2 : new MailTesterError(String(err2)) };
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
yield { email, error: err instanceof MailTesterError ? err : new MailTesterError(String(err)) };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
var index_default = MailTester;
|
|
146
|
+
export {
|
|
147
|
+
DEFAULT_BASE_URL,
|
|
148
|
+
FREE_KEY,
|
|
149
|
+
MailTester,
|
|
150
|
+
MailTesterError,
|
|
151
|
+
MailTesterKeyError,
|
|
152
|
+
MailTesterRateLimitError,
|
|
153
|
+
VERSION,
|
|
154
|
+
index_default as default
|
|
155
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,68 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mailtester-ninja/client",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Official client for the MailTester Ninja email verification API: check in real time if an email address is valid and deliverable.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"email",
|
|
7
|
+
"email-verification",
|
|
8
|
+
"email-validation",
|
|
9
|
+
"email-verifier",
|
|
10
|
+
"email-checker",
|
|
11
|
+
"smtp",
|
|
12
|
+
"catch-all",
|
|
13
|
+
"disposable-email",
|
|
14
|
+
"mailtester",
|
|
15
|
+
"api-client"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://mailtester.ninja/api/",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/mailtester-ninja/mailtester-js/issues"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/mailtester-ninja/mailtester-js.git"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"author": "MailTester Ninja (https://mailtester.ninja)",
|
|
27
|
+
"type": "module",
|
|
28
|
+
"main": "./dist/index.cjs",
|
|
29
|
+
"module": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"import": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"default": "./dist/index.js"
|
|
36
|
+
},
|
|
37
|
+
"require": {
|
|
38
|
+
"types": "./dist/index.d.cts",
|
|
39
|
+
"default": "./dist/index.cjs"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist",
|
|
45
|
+
"README.md",
|
|
46
|
+
"LICENSE"
|
|
47
|
+
],
|
|
48
|
+
"sideEffects": false,
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=18"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public",
|
|
54
|
+
"provenance": true
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean --target node18",
|
|
58
|
+
"typecheck": "tsc --noEmit",
|
|
59
|
+
"test": "vitest run",
|
|
60
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@types/node": "20.17.10",
|
|
64
|
+
"tsup": "8.3.5",
|
|
65
|
+
"typescript": "5.6.3",
|
|
66
|
+
"vitest": "2.1.8"
|
|
67
|
+
}
|
|
68
|
+
}
|