linkedin-secret-sauce 0.10.1 → 0.11.1
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 +512 -232
- package/dist/enrichment/auth/smartlead-auth.d.ts +3 -3
- package/dist/enrichment/auth/smartlead-auth.js +25 -25
- package/dist/enrichment/index.d.ts +6 -4
- package/dist/enrichment/index.js +45 -13
- package/dist/enrichment/matching.d.ts +8 -3
- package/dist/enrichment/matching.js +7 -5
- package/dist/enrichment/orchestrator.js +48 -9
- package/dist/enrichment/providers/bouncer.d.ts +67 -0
- package/dist/enrichment/providers/bouncer.js +233 -0
- package/dist/enrichment/providers/construct.js +72 -14
- package/dist/enrichment/providers/hunter.js +6 -60
- package/dist/enrichment/providers/index.d.ts +2 -1
- package/dist/enrichment/providers/index.js +11 -3
- package/dist/enrichment/providers/ldd.js +5 -47
- package/dist/enrichment/providers/smartprospect.js +9 -14
- package/dist/enrichment/providers/snovio.d.ts +58 -0
- package/dist/enrichment/providers/snovio.js +286 -0
- package/dist/enrichment/types.d.ts +133 -10
- package/dist/enrichment/types.js +28 -6
- package/dist/enrichment/utils/http-retry.d.ts +96 -0
- package/dist/enrichment/utils/http-retry.js +162 -0
- package/dist/enrichment/verification/index.d.ts +1 -1
- package/dist/enrichment/verification/index.js +3 -1
- package/dist/enrichment/verification/mx.d.ts +33 -0
- package/dist/enrichment/verification/mx.js +367 -7
- package/dist/index.d.ts +196 -6
- package/dist/index.js +159 -12
- package/dist/parsers/search-parser.js +7 -3
- package/package.json +10 -3
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snov.io Email Finder Provider
|
|
3
|
+
*
|
|
4
|
+
* Provides email finding with 98% delivery rate and built-in verification.
|
|
5
|
+
* Best for finding emails when pattern guessing fails or on catch-all domains.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Email finding by name + domain
|
|
9
|
+
* - Built-in email verification
|
|
10
|
+
* - Database of 70M+ contacts
|
|
11
|
+
* - LinkedIn profile support
|
|
12
|
+
*
|
|
13
|
+
* @see https://snov.io/api
|
|
14
|
+
*/
|
|
15
|
+
import type { EnrichmentCandidate, ProviderResult, ProviderMultiResult, SnovioConfig, SnovioGetEmailsResponse, SnovioVerificationStatus } from '../types';
|
|
16
|
+
/**
|
|
17
|
+
* Create the Snov.io email finder provider
|
|
18
|
+
*
|
|
19
|
+
* This provider uses Snov.io's database to find verified emails
|
|
20
|
+
* when pattern guessing fails or for catch-all domains.
|
|
21
|
+
*/
|
|
22
|
+
export declare function createSnovioProvider(config: SnovioConfig): (candidate: EnrichmentCandidate) => Promise<ProviderResult | ProviderMultiResult | null>;
|
|
23
|
+
/**
|
|
24
|
+
* Standalone function to find emails via Snov.io
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const result = await findEmailsWithSnovio(
|
|
29
|
+
* 'John',
|
|
30
|
+
* 'Doe',
|
|
31
|
+
* 'example.com',
|
|
32
|
+
* {
|
|
33
|
+
* userId: process.env.SNOVIO_USER_ID,
|
|
34
|
+
* apiSecret: process.env.SNOVIO_API_SECRET,
|
|
35
|
+
* }
|
|
36
|
+
* );
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function findEmailsWithSnovio(firstName: string, lastName: string, domain: string, config: SnovioConfig): Promise<SnovioGetEmailsResponse | null>;
|
|
40
|
+
/**
|
|
41
|
+
* Verify a single email via Snov.io
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* const result = await verifyEmailWithSnovio('test@example.com', {
|
|
46
|
+
* userId: process.env.SNOVIO_USER_ID,
|
|
47
|
+
* apiSecret: process.env.SNOVIO_API_SECRET,
|
|
48
|
+
* });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export declare function verifyEmailWithSnovio(email: string, config: SnovioConfig): Promise<{
|
|
52
|
+
email: string;
|
|
53
|
+
status: SnovioVerificationStatus;
|
|
54
|
+
} | null>;
|
|
55
|
+
/**
|
|
56
|
+
* Clear the cached access token (useful for testing)
|
|
57
|
+
*/
|
|
58
|
+
export declare function clearSnovioTokenCache(): void;
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Snov.io Email Finder Provider
|
|
4
|
+
*
|
|
5
|
+
* Provides email finding with 98% delivery rate and built-in verification.
|
|
6
|
+
* Best for finding emails when pattern guessing fails or on catch-all domains.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Email finding by name + domain
|
|
10
|
+
* - Built-in email verification
|
|
11
|
+
* - Database of 70M+ contacts
|
|
12
|
+
* - LinkedIn profile support
|
|
13
|
+
*
|
|
14
|
+
* @see https://snov.io/api
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.createSnovioProvider = createSnovioProvider;
|
|
18
|
+
exports.findEmailsWithSnovio = findEmailsWithSnovio;
|
|
19
|
+
exports.verifyEmailWithSnovio = verifyEmailWithSnovio;
|
|
20
|
+
exports.clearSnovioTokenCache = clearSnovioTokenCache;
|
|
21
|
+
const DEFAULT_API_URL = 'https://api.snov.io';
|
|
22
|
+
const DEFAULT_TIMEOUT_MS = 30000;
|
|
23
|
+
// Token cache
|
|
24
|
+
let cachedAccessToken = null;
|
|
25
|
+
let tokenExpiresAt = 0;
|
|
26
|
+
/**
|
|
27
|
+
* Map Snov.io verification status to confidence score
|
|
28
|
+
*/
|
|
29
|
+
function statusToConfidence(status) {
|
|
30
|
+
switch (status) {
|
|
31
|
+
case 'valid':
|
|
32
|
+
return 95; // High confidence - verified email
|
|
33
|
+
case 'catch_all':
|
|
34
|
+
return 60; // Medium confidence - catch-all domain
|
|
35
|
+
case 'unverifiable':
|
|
36
|
+
return 40; // Low confidence - could not verify
|
|
37
|
+
case 'not_valid':
|
|
38
|
+
return 0; // Invalid email
|
|
39
|
+
case 'unknown':
|
|
40
|
+
default:
|
|
41
|
+
return 30;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Extract name and domain from candidate
|
|
46
|
+
*/
|
|
47
|
+
function extractNameAndDomain(candidate) {
|
|
48
|
+
const firstName = candidate.firstName ||
|
|
49
|
+
candidate.first_name ||
|
|
50
|
+
candidate.first ||
|
|
51
|
+
candidate.name?.split(' ')?.[0] ||
|
|
52
|
+
'';
|
|
53
|
+
const lastName = candidate.lastName ||
|
|
54
|
+
candidate.last_name ||
|
|
55
|
+
candidate.last ||
|
|
56
|
+
candidate.name?.split(' ')?.slice(1).join(' ') ||
|
|
57
|
+
'';
|
|
58
|
+
const domain = candidate.domain ||
|
|
59
|
+
candidate.companyDomain ||
|
|
60
|
+
candidate.company_domain ||
|
|
61
|
+
'';
|
|
62
|
+
if (!firstName || !domain) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return { firstName, lastName, domain };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get OAuth access token from Snov.io
|
|
69
|
+
*/
|
|
70
|
+
async function getAccessToken(userId, apiSecret, apiUrl, timeoutMs) {
|
|
71
|
+
// Return cached token if valid
|
|
72
|
+
if (cachedAccessToken && Date.now() < tokenExpiresAt) {
|
|
73
|
+
return cachedAccessToken;
|
|
74
|
+
}
|
|
75
|
+
const controller = new AbortController();
|
|
76
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
77
|
+
try {
|
|
78
|
+
const response = await fetch(`${apiUrl}/v1/oauth/access_token`, {
|
|
79
|
+
method: 'POST',
|
|
80
|
+
headers: {
|
|
81
|
+
'Content-Type': 'application/json',
|
|
82
|
+
},
|
|
83
|
+
body: JSON.stringify({
|
|
84
|
+
grant_type: 'client_credentials',
|
|
85
|
+
client_id: userId,
|
|
86
|
+
client_secret: apiSecret,
|
|
87
|
+
}),
|
|
88
|
+
signal: controller.signal,
|
|
89
|
+
});
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const data = await response.json();
|
|
94
|
+
// Cache token with 5 minute buffer
|
|
95
|
+
cachedAccessToken = data.access_token;
|
|
96
|
+
tokenExpiresAt = Date.now() + (data.expires_in - 300) * 1000;
|
|
97
|
+
return data.access_token;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
clearTimeout(timeoutId);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Find emails using Snov.io's get-emails-from-name API
|
|
108
|
+
*/
|
|
109
|
+
async function findEmailsByName(firstName, lastName, domain, accessToken, apiUrl, timeoutMs) {
|
|
110
|
+
const controller = new AbortController();
|
|
111
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
112
|
+
try {
|
|
113
|
+
const params = new URLSearchParams({
|
|
114
|
+
access_token: accessToken,
|
|
115
|
+
firstName,
|
|
116
|
+
lastName,
|
|
117
|
+
domain,
|
|
118
|
+
});
|
|
119
|
+
const response = await fetch(`${apiUrl}/v1/get-emails-from-names?${params}`, {
|
|
120
|
+
method: 'GET',
|
|
121
|
+
headers: {
|
|
122
|
+
'Accept': 'application/json',
|
|
123
|
+
},
|
|
124
|
+
signal: controller.signal,
|
|
125
|
+
});
|
|
126
|
+
if (!response.ok) {
|
|
127
|
+
// Handle rate limiting
|
|
128
|
+
if (response.status === 429) {
|
|
129
|
+
throw new Error('Rate limited by Snov.io API');
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
const data = await response.json();
|
|
134
|
+
return data;
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
138
|
+
return null; // Timeout
|
|
139
|
+
}
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
clearTimeout(timeoutId);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Create the Snov.io email finder provider
|
|
148
|
+
*
|
|
149
|
+
* This provider uses Snov.io's database to find verified emails
|
|
150
|
+
* when pattern guessing fails or for catch-all domains.
|
|
151
|
+
*/
|
|
152
|
+
function createSnovioProvider(config) {
|
|
153
|
+
const { userId, apiSecret, apiUrl = DEFAULT_API_URL, timeoutMs = DEFAULT_TIMEOUT_MS, } = config;
|
|
154
|
+
if (!userId || !apiSecret) {
|
|
155
|
+
// Return no-op provider if not configured
|
|
156
|
+
const noop = async () => null;
|
|
157
|
+
noop.__name = 'snovio';
|
|
158
|
+
return noop;
|
|
159
|
+
}
|
|
160
|
+
async function findEmails(candidate) {
|
|
161
|
+
const nameAndDomain = extractNameAndDomain(candidate);
|
|
162
|
+
if (!nameAndDomain) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
const { firstName, lastName, domain } = nameAndDomain;
|
|
166
|
+
// Get access token
|
|
167
|
+
const accessToken = await getAccessToken(userId, apiSecret, apiUrl, timeoutMs);
|
|
168
|
+
if (!accessToken) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
// Find emails
|
|
172
|
+
const result = await findEmailsByName(firstName, lastName, domain, accessToken, apiUrl, timeoutMs);
|
|
173
|
+
if (!result || !result.success || !result.data?.emails?.length) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
// Convert to provider multi-result format
|
|
177
|
+
const emails = result.data.emails
|
|
178
|
+
.filter((e) => e.email && e.emailStatus !== 'not_valid')
|
|
179
|
+
.map((e) => ({
|
|
180
|
+
email: e.email,
|
|
181
|
+
verified: e.emailStatus === 'valid',
|
|
182
|
+
confidence: statusToConfidence(e.emailStatus),
|
|
183
|
+
isCatchAll: e.emailStatus === 'catch_all',
|
|
184
|
+
metadata: {
|
|
185
|
+
snovioStatus: e.emailStatus,
|
|
186
|
+
firstName: e.firstName,
|
|
187
|
+
lastName: e.lastName,
|
|
188
|
+
position: e.position,
|
|
189
|
+
companyName: e.companyName,
|
|
190
|
+
sourcePage: e.sourcePage,
|
|
191
|
+
type: e.type,
|
|
192
|
+
},
|
|
193
|
+
}));
|
|
194
|
+
if (emails.length === 0) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
// Sort by confidence
|
|
198
|
+
emails.sort((a, b) => (b.confidence ?? 0) - (a.confidence ?? 0));
|
|
199
|
+
return { emails };
|
|
200
|
+
}
|
|
201
|
+
// Mark provider name for orchestrator
|
|
202
|
+
findEmails.__name = 'snovio';
|
|
203
|
+
return findEmails;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Standalone function to find emails via Snov.io
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* ```typescript
|
|
210
|
+
* const result = await findEmailsWithSnovio(
|
|
211
|
+
* 'John',
|
|
212
|
+
* 'Doe',
|
|
213
|
+
* 'example.com',
|
|
214
|
+
* {
|
|
215
|
+
* userId: process.env.SNOVIO_USER_ID,
|
|
216
|
+
* apiSecret: process.env.SNOVIO_API_SECRET,
|
|
217
|
+
* }
|
|
218
|
+
* );
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
async function findEmailsWithSnovio(firstName, lastName, domain, config) {
|
|
222
|
+
const { userId, apiSecret, apiUrl = DEFAULT_API_URL, timeoutMs = DEFAULT_TIMEOUT_MS, } = config;
|
|
223
|
+
const accessToken = await getAccessToken(userId, apiSecret, apiUrl, timeoutMs);
|
|
224
|
+
if (!accessToken) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
return findEmailsByName(firstName, lastName, domain, accessToken, apiUrl, timeoutMs);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Verify a single email via Snov.io
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* ```typescript
|
|
234
|
+
* const result = await verifyEmailWithSnovio('test@example.com', {
|
|
235
|
+
* userId: process.env.SNOVIO_USER_ID,
|
|
236
|
+
* apiSecret: process.env.SNOVIO_API_SECRET,
|
|
237
|
+
* });
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
async function verifyEmailWithSnovio(email, config) {
|
|
241
|
+
const { userId, apiSecret, apiUrl = DEFAULT_API_URL, timeoutMs = DEFAULT_TIMEOUT_MS, } = config;
|
|
242
|
+
const accessToken = await getAccessToken(userId, apiSecret, apiUrl, timeoutMs);
|
|
243
|
+
if (!accessToken) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
const controller = new AbortController();
|
|
247
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
248
|
+
try {
|
|
249
|
+
const params = new URLSearchParams({
|
|
250
|
+
access_token: accessToken,
|
|
251
|
+
email,
|
|
252
|
+
});
|
|
253
|
+
const response = await fetch(`${apiUrl}/v1/email-verifier?${params}`, {
|
|
254
|
+
method: 'GET',
|
|
255
|
+
headers: {
|
|
256
|
+
'Accept': 'application/json',
|
|
257
|
+
},
|
|
258
|
+
signal: controller.signal,
|
|
259
|
+
});
|
|
260
|
+
if (!response.ok) {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
const data = await response.json();
|
|
264
|
+
if (!data.success || !data.data?.emails?.length) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
const result = data.data.emails[0];
|
|
268
|
+
return {
|
|
269
|
+
email: result.email,
|
|
270
|
+
status: result.result,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
finally {
|
|
277
|
+
clearTimeout(timeoutId);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Clear the cached access token (useful for testing)
|
|
282
|
+
*/
|
|
283
|
+
function clearSnovioTokenCache() {
|
|
284
|
+
cachedAccessToken = null;
|
|
285
|
+
tokenExpiresAt = 0;
|
|
286
|
+
}
|
|
@@ -126,12 +126,6 @@ export interface EnrichmentCandidate {
|
|
|
126
126
|
export interface HunterConfig {
|
|
127
127
|
apiKey: string;
|
|
128
128
|
}
|
|
129
|
-
/**
|
|
130
|
-
* Apollo.io provider configuration
|
|
131
|
-
*/
|
|
132
|
-
export interface ApolloConfig {
|
|
133
|
-
apiKey: string;
|
|
134
|
-
}
|
|
135
129
|
/**
|
|
136
130
|
* SmartProspect/Smartlead provider configuration
|
|
137
131
|
*
|
|
@@ -166,6 +160,109 @@ export interface LddConfig {
|
|
|
166
160
|
export interface DropcontactConfig {
|
|
167
161
|
apiKey: string;
|
|
168
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Bouncer.io provider configuration
|
|
165
|
+
*
|
|
166
|
+
* Bouncer provides SMTP email verification with 99%+ accuracy.
|
|
167
|
+
* Best for verifying pattern-guessed emails on non-catch-all domains.
|
|
168
|
+
*
|
|
169
|
+
* @see https://docs.usebouncer.com
|
|
170
|
+
*/
|
|
171
|
+
export interface BouncerConfig {
|
|
172
|
+
/** Bouncer API key */
|
|
173
|
+
apiKey: string;
|
|
174
|
+
/** API URL override (default: https://api.usebouncer.com/v1.1) */
|
|
175
|
+
apiUrl?: string;
|
|
176
|
+
/** Timeout in ms (default: 30000) */
|
|
177
|
+
timeoutMs?: number;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Bouncer verification result status
|
|
181
|
+
*/
|
|
182
|
+
export type BouncerStatus = 'deliverable' | 'undeliverable' | 'risky' | 'unknown';
|
|
183
|
+
/**
|
|
184
|
+
* Bouncer verification result reason
|
|
185
|
+
*/
|
|
186
|
+
export type BouncerReason = 'accepted_email' | 'rejected_email' | 'invalid_domain' | 'invalid_email' | 'unavailable_smtp' | 'dns_error' | 'low_deliverability' | 'low_quality' | 'catch_all' | 'full_mailbox' | 'role_account' | 'disposable' | 'timeout' | 'unknown';
|
|
187
|
+
/**
|
|
188
|
+
* Bouncer API response for single email verification
|
|
189
|
+
*/
|
|
190
|
+
export interface BouncerVerifyResponse {
|
|
191
|
+
status: BouncerStatus;
|
|
192
|
+
reason: BouncerReason;
|
|
193
|
+
email: string;
|
|
194
|
+
domain: string;
|
|
195
|
+
account: string;
|
|
196
|
+
/** Whether this is a free email provider (Gmail, Yahoo, etc.) */
|
|
197
|
+
free: boolean;
|
|
198
|
+
/** Whether this is a disposable email */
|
|
199
|
+
disposable: boolean;
|
|
200
|
+
/** Whether this is a role-based email */
|
|
201
|
+
role: boolean;
|
|
202
|
+
/** Whether domain is catch-all (accepts all emails) */
|
|
203
|
+
acceptAll: boolean;
|
|
204
|
+
/** Did you mean suggestion for typos */
|
|
205
|
+
didYouMean?: string;
|
|
206
|
+
/** DNS information */
|
|
207
|
+
dns?: {
|
|
208
|
+
type: string;
|
|
209
|
+
record: string;
|
|
210
|
+
};
|
|
211
|
+
/** Toxicity score (0-5, higher = more risky) */
|
|
212
|
+
toxicity?: number;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Snov.io provider configuration
|
|
216
|
+
*
|
|
217
|
+
* Snov.io provides email finding with 98% delivery rate and built-in verification.
|
|
218
|
+
* Best for finding emails when pattern guessing fails or on catch-all domains.
|
|
219
|
+
*
|
|
220
|
+
* @see https://snov.io/api
|
|
221
|
+
*/
|
|
222
|
+
export interface SnovioConfig {
|
|
223
|
+
/** Snov.io API user ID */
|
|
224
|
+
userId: string;
|
|
225
|
+
/** Snov.io API secret */
|
|
226
|
+
apiSecret: string;
|
|
227
|
+
/** API URL override (default: https://api.snov.io) */
|
|
228
|
+
apiUrl?: string;
|
|
229
|
+
/** Timeout in ms (default: 30000) */
|
|
230
|
+
timeoutMs?: number;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Snov.io email verification status
|
|
234
|
+
*/
|
|
235
|
+
export type SnovioVerificationStatus = 'valid' | 'not_valid' | 'catch_all' | 'unverifiable' | 'unknown';
|
|
236
|
+
/**
|
|
237
|
+
* Snov.io email result
|
|
238
|
+
*/
|
|
239
|
+
export interface SnovioEmailResult {
|
|
240
|
+
email: string;
|
|
241
|
+
emailStatus: SnovioVerificationStatus;
|
|
242
|
+
firstName?: string;
|
|
243
|
+
lastName?: string;
|
|
244
|
+
position?: string;
|
|
245
|
+
sourcePage?: string;
|
|
246
|
+
companyName?: string;
|
|
247
|
+
type?: 'prospect' | 'personal';
|
|
248
|
+
status?: string;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Snov.io get-emails-from-name API response
|
|
252
|
+
*/
|
|
253
|
+
export interface SnovioGetEmailsResponse {
|
|
254
|
+
success: boolean;
|
|
255
|
+
data: {
|
|
256
|
+
firstName: string;
|
|
257
|
+
lastName: string;
|
|
258
|
+
emails: SnovioEmailResult[];
|
|
259
|
+
};
|
|
260
|
+
params?: {
|
|
261
|
+
firstName: string;
|
|
262
|
+
lastName: string;
|
|
263
|
+
domain: string;
|
|
264
|
+
};
|
|
265
|
+
}
|
|
169
266
|
/**
|
|
170
267
|
* Email construction provider configuration (no API key needed)
|
|
171
268
|
*/
|
|
@@ -174,6 +271,8 @@ export interface ConstructConfig {
|
|
|
174
271
|
maxAttempts?: number;
|
|
175
272
|
/** Timeout for MX verification in ms (default: 5000) */
|
|
176
273
|
timeoutMs?: number;
|
|
274
|
+
/** Delay between SMTP verification checks in ms (default: 2000) */
|
|
275
|
+
smtpVerifyDelayMs?: number;
|
|
177
276
|
}
|
|
178
277
|
/**
|
|
179
278
|
* All provider configurations
|
|
@@ -183,8 +282,11 @@ export interface ProvidersConfig {
|
|
|
183
282
|
ldd?: LddConfig;
|
|
184
283
|
smartprospect?: SmartProspectConfig;
|
|
185
284
|
hunter?: HunterConfig;
|
|
186
|
-
apollo?: ApolloConfig;
|
|
187
285
|
dropcontact?: DropcontactConfig;
|
|
286
|
+
/** Bouncer.io for SMTP email verification (99%+ accuracy) */
|
|
287
|
+
bouncer?: BouncerConfig;
|
|
288
|
+
/** Snov.io for email finding (98% delivery rate) */
|
|
289
|
+
snovio?: SnovioConfig;
|
|
188
290
|
}
|
|
189
291
|
/**
|
|
190
292
|
* Options for enrichment operations
|
|
@@ -194,7 +296,7 @@ export interface EnrichmentOptions {
|
|
|
194
296
|
maxCostPerEmail?: number;
|
|
195
297
|
/** Minimum confidence threshold 0-100 (default: 0) */
|
|
196
298
|
confidenceThreshold?: number;
|
|
197
|
-
/** Provider order (default: ['
|
|
299
|
+
/** Provider order (default: ['ldd', 'smartprospect', 'construct', 'bouncer', 'snovio', 'hunter']) */
|
|
198
300
|
providerOrder?: ProviderName[];
|
|
199
301
|
/** Retry delay in ms on transient errors (default: 200) */
|
|
200
302
|
retryMs?: number;
|
|
@@ -263,13 +365,34 @@ export interface EnrichmentClient {
|
|
|
263
365
|
/**
|
|
264
366
|
* Available provider names
|
|
265
367
|
*/
|
|
266
|
-
export type ProviderName = "construct" | "ldd" | "smartprospect" | "hunter" | "
|
|
368
|
+
export type ProviderName = "construct" | "ldd" | "smartprospect" | "hunter" | "dropcontact" | "bouncer" | "snovio";
|
|
267
369
|
/**
|
|
268
|
-
* Default provider order
|
|
370
|
+
* Default provider order - 2-Phase Strategy
|
|
371
|
+
*
|
|
372
|
+
* PHASE 1 - Free lookups (run in parallel):
|
|
373
|
+
* - ldd: LinkedIn Data Dump - real verified emails (FREE with subscription)
|
|
374
|
+
* - smartprospect: SmartLead API - real verified emails (FREE with subscription)
|
|
375
|
+
* - construct: Pattern guessing + MX check (FREE)
|
|
376
|
+
*
|
|
377
|
+
* PHASE 2 - Paid verification/finding (only if Phase 1 inconclusive):
|
|
378
|
+
* - bouncer: SMTP verify constructed emails ($0.006/email)
|
|
379
|
+
* - snovio: Email finder for catch-all domains ($0.02/email)
|
|
380
|
+
* - hunter: Hunter.io fallback ($0.005/email)
|
|
381
|
+
*
|
|
382
|
+
* Note: dropcontact available but not in default order (expensive at $0.01)
|
|
269
383
|
*/
|
|
270
384
|
export declare const DEFAULT_PROVIDER_ORDER: ProviderName[];
|
|
271
385
|
/**
|
|
272
386
|
* Provider costs in USD per lookup
|
|
387
|
+
*
|
|
388
|
+
* Costs based on 2025 pricing:
|
|
389
|
+
* - ldd: FREE (subscription-based)
|
|
390
|
+
* - smartprospect: FREE (included in SmartLead subscription)
|
|
391
|
+
* - construct: FREE (pattern guessing + MX check)
|
|
392
|
+
* - bouncer: $0.006/email (SMTP verification, 99%+ accuracy)
|
|
393
|
+
* - snovio: $0.02/email (email finding + verification)
|
|
394
|
+
* - hunter: $0.005/email
|
|
395
|
+
* - dropcontact: $0.01/email (not in default order)
|
|
273
396
|
*/
|
|
274
397
|
export declare const PROVIDER_COSTS: Record<ProviderName, number>;
|
|
275
398
|
/**
|
package/dist/enrichment/types.js
CHANGED
|
@@ -8,26 +8,48 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.SMARTPROSPECT_SUB_INDUSTRIES = exports.PROVIDER_COSTS = exports.DEFAULT_PROVIDER_ORDER = void 0;
|
|
10
10
|
/**
|
|
11
|
-
* Default provider order
|
|
11
|
+
* Default provider order - 2-Phase Strategy
|
|
12
|
+
*
|
|
13
|
+
* PHASE 1 - Free lookups (run in parallel):
|
|
14
|
+
* - ldd: LinkedIn Data Dump - real verified emails (FREE with subscription)
|
|
15
|
+
* - smartprospect: SmartLead API - real verified emails (FREE with subscription)
|
|
16
|
+
* - construct: Pattern guessing + MX check (FREE)
|
|
17
|
+
*
|
|
18
|
+
* PHASE 2 - Paid verification/finding (only if Phase 1 inconclusive):
|
|
19
|
+
* - bouncer: SMTP verify constructed emails ($0.006/email)
|
|
20
|
+
* - snovio: Email finder for catch-all domains ($0.02/email)
|
|
21
|
+
* - hunter: Hunter.io fallback ($0.005/email)
|
|
22
|
+
*
|
|
23
|
+
* Note: dropcontact available but not in default order (expensive at $0.01)
|
|
12
24
|
*/
|
|
13
25
|
exports.DEFAULT_PROVIDER_ORDER = [
|
|
14
|
-
"construct",
|
|
15
26
|
"ldd",
|
|
16
27
|
"smartprospect",
|
|
28
|
+
"construct",
|
|
29
|
+
"bouncer",
|
|
30
|
+
"snovio",
|
|
17
31
|
"hunter",
|
|
18
|
-
"apollo",
|
|
19
|
-
"dropcontact",
|
|
20
32
|
];
|
|
21
33
|
/**
|
|
22
34
|
* Provider costs in USD per lookup
|
|
35
|
+
*
|
|
36
|
+
* Costs based on 2025 pricing:
|
|
37
|
+
* - ldd: FREE (subscription-based)
|
|
38
|
+
* - smartprospect: FREE (included in SmartLead subscription)
|
|
39
|
+
* - construct: FREE (pattern guessing + MX check)
|
|
40
|
+
* - bouncer: $0.006/email (SMTP verification, 99%+ accuracy)
|
|
41
|
+
* - snovio: $0.02/email (email finding + verification)
|
|
42
|
+
* - hunter: $0.005/email
|
|
43
|
+
* - dropcontact: $0.01/email (not in default order)
|
|
23
44
|
*/
|
|
24
45
|
exports.PROVIDER_COSTS = {
|
|
25
46
|
construct: 0,
|
|
26
47
|
ldd: 0,
|
|
27
|
-
smartprospect: 0
|
|
48
|
+
smartprospect: 0,
|
|
28
49
|
hunter: 0.005,
|
|
29
|
-
apollo: 0,
|
|
30
50
|
dropcontact: 0.01,
|
|
51
|
+
bouncer: 0.006,
|
|
52
|
+
snovio: 0.02,
|
|
31
53
|
};
|
|
32
54
|
/**
|
|
33
55
|
* SmartProspect Sub-Industry values (exact API values - partial list)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Retry Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared utilities for HTTP requests with retry logic, rate limit handling,
|
|
5
|
+
* and exponential backoff. Used across all enrichment providers.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Delay execution for specified milliseconds
|
|
9
|
+
*/
|
|
10
|
+
export declare function delay(ms: number): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Check if a value is truthy (not null, undefined, or empty string)
|
|
13
|
+
*/
|
|
14
|
+
export declare function truthy(v: unknown): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Map common verification status strings to boolean
|
|
17
|
+
*/
|
|
18
|
+
export declare function mapVerifiedStatus(status: string | undefined | null): boolean | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Options for fetch with retry
|
|
21
|
+
*/
|
|
22
|
+
export interface FetchWithRetryOptions {
|
|
23
|
+
/** Number of retries (default: 1) */
|
|
24
|
+
retries?: number;
|
|
25
|
+
/** Initial backoff in ms (default: 200) */
|
|
26
|
+
backoffMs?: number;
|
|
27
|
+
/** Timeout in ms (default: 30000) */
|
|
28
|
+
timeoutMs?: number;
|
|
29
|
+
/** HTTP status codes that should trigger retry (default: [429, 502, 503, 504]) */
|
|
30
|
+
retryOnStatus?: number[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Generic API response type
|
|
34
|
+
*/
|
|
35
|
+
export interface ApiResponse {
|
|
36
|
+
data?: unknown;
|
|
37
|
+
result?: unknown;
|
|
38
|
+
people?: unknown[];
|
|
39
|
+
matches?: unknown[];
|
|
40
|
+
emails?: unknown[];
|
|
41
|
+
success?: boolean;
|
|
42
|
+
message?: string;
|
|
43
|
+
[key: string]: unknown;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* HTTP request with retry on rate limit and transient errors
|
|
47
|
+
*
|
|
48
|
+
* Features:
|
|
49
|
+
* - Exponential backoff on rate limit (429)
|
|
50
|
+
* - Retry on server errors (502, 503, 504)
|
|
51
|
+
* - Configurable timeout
|
|
52
|
+
* - Returns typed response
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* const response = await fetchWithRetry<MyApiResponse>(
|
|
57
|
+
* 'https://api.example.com/data',
|
|
58
|
+
* {
|
|
59
|
+
* method: 'POST',
|
|
60
|
+
* headers: { 'Content-Type': 'application/json' },
|
|
61
|
+
* body: JSON.stringify({ query: 'test' }),
|
|
62
|
+
* },
|
|
63
|
+
* { retries: 2, backoffMs: 300 }
|
|
64
|
+
* );
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare function fetchWithRetry<T = ApiResponse>(url: string, init?: RequestInit, options?: FetchWithRetryOptions): Promise<T>;
|
|
68
|
+
/**
|
|
69
|
+
* Simpler GET request with retry (no request body)
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* const data = await getWithRetry<UserData>(
|
|
74
|
+
* `https://api.example.com/users/${id}`,
|
|
75
|
+
* { 'Authorization': `Bearer ${token}` }
|
|
76
|
+
* );
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare function getWithRetry<T = ApiResponse>(url: string, headers?: Record<string, string>, options?: FetchWithRetryOptions): Promise<T>;
|
|
80
|
+
/**
|
|
81
|
+
* POST request with JSON body and retry
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* const result = await postWithRetry<SearchResult>(
|
|
86
|
+
* 'https://api.example.com/search',
|
|
87
|
+
* { query: 'test', limit: 10 },
|
|
88
|
+
* { 'X-Api-Key': apiKey }
|
|
89
|
+
* );
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export declare function postWithRetry<T = ApiResponse>(url: string, body: Record<string, unknown>, headers?: Record<string, string>, options?: FetchWithRetryOptions): Promise<T>;
|
|
93
|
+
/**
|
|
94
|
+
* Safe JSON parse with fallback
|
|
95
|
+
*/
|
|
96
|
+
export declare function safeJsonParse<T>(str: string, fallback: T): T;
|