@mailtester-ninja/client 1.0.0 → 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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # MailTester Ninja for JavaScript and TypeScript
2
2
 
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.
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
4
 
5
5
  - Zero dependencies, typed, ESM and CommonJS
6
6
  - Node.js 18+, Deno, Bun and browsers (CORS is open)
@@ -48,14 +48,32 @@ for await (const { email, result, error } of mt.verifyMany(['info@apple.com', 's
48
48
  }
49
49
  ```
50
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
+
51
69
  ## Errors
52
70
 
53
71
  An error is never a verdict: do not drop an address because of it.
54
72
 
55
73
  | Error | When |
56
74
  |-------|------|
57
- | `MailTesterKeyError` | The key is invalid or disabled. |
58
- | `MailTesterRateLimitError` | A limit was reached (HTTP 429). `retryAfterSeconds` is set on the free tier. |
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`. |
59
77
  | `MailTesterError` | Any other HTTP or network error (`status` when there is one). |
60
78
 
61
79
  ## Keys and limits
package/dist/index.cjs CHANGED
@@ -30,7 +30,7 @@ __export(index_exports, {
30
30
  default: () => index_default
31
31
  });
32
32
  module.exports = __toCommonJS(index_exports);
33
- var VERSION = "1.0.0";
33
+ var VERSION = "1.1.0";
34
34
  var DEFAULT_BASE_URL = "https://happy.mailtester.ninja";
35
35
  var FREE_KEY = "free";
36
36
  var MailTesterError = class extends Error {
@@ -90,9 +90,7 @@ var MailTester = class {
90
90
  get isFree() {
91
91
  return this.key === FREE_KEY || this.key.startsWith("mtf_");
92
92
  }
93
- /** Verify one address. Resolves to a verdict, rejects with a MailTesterError when there is none. */
94
- async verify(email) {
95
- const url = `${this.baseUrl}/ninja?email=${encodeURIComponent(email.trim())}&key=${encodeURIComponent(this.key)}`;
93
+ async request(url) {
96
94
  const controller = new AbortController();
97
95
  const timer = setTimeout(() => controller.abort(), this.timeoutMs);
98
96
  let res;
@@ -105,7 +103,11 @@ var MailTester = class {
105
103
  clearTimeout(timer);
106
104
  }
107
105
  const text = await res.text();
108
- const body = parseJson(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)}`);
109
111
  const message = typeof body?.message === "string" ? body.message : text.trim();
110
112
  if (res.status === 429) throw new MailTesterRateLimitError(message, retryAfter(message));
111
113
  if (!res.ok) throw new MailTesterError(message || `HTTP ${res.status}`, res.status);
@@ -113,6 +115,30 @@ var MailTester = class {
113
115
  if (body.code === "--") throw new MailTesterKeyError(message, res.status);
114
116
  return body;
115
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
+ }
116
142
  /** True only when the address is clearly valid and deliverable (`code` "ok"). */
117
143
  async isDeliverable(email) {
118
144
  return (await this.verify(email)).code === "ok";
package/dist/index.d.cts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Official JavaScript and TypeScript client for the MailTester Ninja email verification API.
3
3
  * https://mailtester.ninja/api/
4
4
  */
5
- declare const VERSION = "1.0.0";
5
+ declare const VERSION = "1.1.0";
6
6
  declare const DEFAULT_BASE_URL = "https://happy.mailtester.ninja";
7
7
  /** Keyless free tier: 10 checks a day per IP, one request every 30 seconds. */
8
8
  declare const FREE_KEY = "free";
@@ -27,6 +27,47 @@ interface VerificationResult {
27
27
  /** Requests made with the key over the last 10 seconds. */
28
28
  rate?: number;
29
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
+ }
30
71
  interface MailTesterOptions {
31
72
  /**
32
73
  * `free` (no signup), a free token `mtf_...` from https://mailtester.ninja/free-email-verification-api/,
@@ -72,8 +113,16 @@ declare class MailTester {
72
113
  constructor(options?: MailTesterOptions | string);
73
114
  /** True on the free tier (`free` or a free token), limited to one request every 30 seconds. */
74
115
  get isFree(): boolean;
116
+ private request;
75
117
  /** Verify one address. Resolves to a verdict, rejects with a MailTesterError when there is none. */
76
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>;
77
126
  /** True only when the address is clearly valid and deliverable (`code` "ok"). */
78
127
  isDeliverable(email: string): Promise<boolean>;
79
128
  /**
@@ -83,4 +132,4 @@ declare class MailTester {
83
132
  verifyMany(emails: Iterable<string> | AsyncIterable<string>, options?: VerifyManyOptions): AsyncGenerator<VerifyManyItem>;
84
133
  }
85
134
 
86
- export { DEFAULT_BASE_URL, FREE_KEY, MailTester, MailTesterError, MailTesterKeyError, type MailTesterOptions, MailTesterRateLimitError, VERSION, type Verdict, type VerificationResult, type VerifyManyItem, type VerifyManyOptions, MailTester as default };
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 CHANGED
@@ -2,7 +2,7 @@
2
2
  * Official JavaScript and TypeScript client for the MailTester Ninja email verification API.
3
3
  * https://mailtester.ninja/api/
4
4
  */
5
- declare const VERSION = "1.0.0";
5
+ declare const VERSION = "1.1.0";
6
6
  declare const DEFAULT_BASE_URL = "https://happy.mailtester.ninja";
7
7
  /** Keyless free tier: 10 checks a day per IP, one request every 30 seconds. */
8
8
  declare const FREE_KEY = "free";
@@ -27,6 +27,47 @@ interface VerificationResult {
27
27
  /** Requests made with the key over the last 10 seconds. */
28
28
  rate?: number;
29
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
+ }
30
71
  interface MailTesterOptions {
31
72
  /**
32
73
  * `free` (no signup), a free token `mtf_...` from https://mailtester.ninja/free-email-verification-api/,
@@ -72,8 +113,16 @@ declare class MailTester {
72
113
  constructor(options?: MailTesterOptions | string);
73
114
  /** True on the free tier (`free` or a free token), limited to one request every 30 seconds. */
74
115
  get isFree(): boolean;
116
+ private request;
75
117
  /** Verify one address. Resolves to a verdict, rejects with a MailTesterError when there is none. */
76
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>;
77
126
  /** True only when the address is clearly valid and deliverable (`code` "ok"). */
78
127
  isDeliverable(email: string): Promise<boolean>;
79
128
  /**
@@ -83,4 +132,4 @@ declare class MailTester {
83
132
  verifyMany(emails: Iterable<string> | AsyncIterable<string>, options?: VerifyManyOptions): AsyncGenerator<VerifyManyItem>;
84
133
  }
85
134
 
86
- export { DEFAULT_BASE_URL, FREE_KEY, MailTester, MailTesterError, MailTesterKeyError, type MailTesterOptions, MailTesterRateLimitError, VERSION, type Verdict, type VerificationResult, type VerifyManyItem, type VerifyManyOptions, MailTester as default };
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 CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- var VERSION = "1.0.0";
2
+ var VERSION = "1.1.0";
3
3
  var DEFAULT_BASE_URL = "https://happy.mailtester.ninja";
4
4
  var FREE_KEY = "free";
5
5
  var MailTesterError = class extends Error {
@@ -59,9 +59,7 @@ var MailTester = class {
59
59
  get isFree() {
60
60
  return this.key === FREE_KEY || this.key.startsWith("mtf_");
61
61
  }
62
- /** Verify one address. Resolves to a verdict, rejects with a MailTesterError when there is none. */
63
- async verify(email) {
64
- const url = `${this.baseUrl}/ninja?email=${encodeURIComponent(email.trim())}&key=${encodeURIComponent(this.key)}`;
62
+ async request(url) {
65
63
  const controller = new AbortController();
66
64
  const timer = setTimeout(() => controller.abort(), this.timeoutMs);
67
65
  let res;
@@ -74,7 +72,11 @@ var MailTester = class {
74
72
  clearTimeout(timer);
75
73
  }
76
74
  const text = await res.text();
77
- const body = parseJson(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)}`);
78
80
  const message = typeof body?.message === "string" ? body.message : text.trim();
79
81
  if (res.status === 429) throw new MailTesterRateLimitError(message, retryAfter(message));
80
82
  if (!res.ok) throw new MailTesterError(message || `HTTP ${res.status}`, res.status);
@@ -82,6 +84,30 @@ var MailTester = class {
82
84
  if (body.code === "--") throw new MailTesterKeyError(message, res.status);
83
85
  return body;
84
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
+ }
85
111
  /** True only when the address is clearly valid and deliverable (`code` "ok"). */
86
112
  async isDeliverable(email) {
87
113
  return (await this.verify(email)).code === "ok";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mailtester-ninja/client",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Official client for the MailTester Ninja email verification API: check in real time if an email address is valid and deliverable.",
5
5
  "keywords": [
6
6
  "email",