linkedin-secret-sauce 0.12.0 → 0.12.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/dist/enrichment/index.d.ts +3 -3
- package/dist/enrichment/index.js +10 -2
- package/dist/enrichment/matching.d.ts +14 -8
- package/dist/enrichment/matching.js +175 -94
- package/dist/enrichment/providers/cosiall.d.ts +27 -0
- package/dist/enrichment/providers/cosiall.js +110 -0
- package/dist/enrichment/providers/index.d.ts +8 -7
- package/dist/enrichment/providers/index.js +3 -1
- package/dist/enrichment/types.d.ts +25 -11
- package/dist/enrichment/types.js +44 -40
- package/package.json +1 -1
|
@@ -40,10 +40,10 @@ export { PROVIDER_COSTS, DEFAULT_PROVIDER_ORDER } from "./types";
|
|
|
40
40
|
export { isPersonalEmail, isBusinessEmail, isPersonalDomain, PERSONAL_DOMAINS, } from "./utils/personal-domains";
|
|
41
41
|
export { isDisposableEmail, isDisposableDomain, DISPOSABLE_DOMAINS, } from "./utils/disposable-domains";
|
|
42
42
|
export { isValidEmailSyntax, isRoleAccount, asciiFold, cleanNamePart, hostnameFromUrl, extractLinkedInUsername, } from "./utils/validation";
|
|
43
|
-
export { verifyEmailMx, checkDomainCatchAll, verifyEmailsExist } from "./verification/mx";
|
|
44
|
-
export { createConstructProvider, createLddProvider, createSmartProspectProvider, createHunterProvider, createDropcontactProvider, createBouncerProvider, createSnovioProvider, verifyEmailWithBouncer, checkCatchAllDomain, verifyEmailsBatch, findEmailsWithSnovio, verifyEmailWithSnovio, clearSnovioTokenCache, } from "./providers";
|
|
43
|
+
export { verifyEmailMx, checkDomainCatchAll, verifyEmailsExist, } from "./verification/mx";
|
|
44
|
+
export { createConstructProvider, createLddProvider, createSmartProspectProvider, createCosiallProvider, createHunterProvider, createDropcontactProvider, createBouncerProvider, createSnovioProvider, verifyEmailWithBouncer, checkCatchAllDomain, verifyEmailsBatch, findEmailsWithSnovio, verifyEmailWithSnovio, clearSnovioTokenCache, } from "./providers";
|
|
45
45
|
export { extractNumericLinkedInId } from "./providers/ldd";
|
|
46
46
|
export { createSmartProspectClient, type SmartProspectClient, type SmartProspectLocationOptions, } from "./providers/smartprospect";
|
|
47
|
-
export { enrichBusinessEmail, enrichBatch, enrichAllEmails, enrichAllBatch } from "./orchestrator";
|
|
47
|
+
export { enrichBusinessEmail, enrichBatch, enrichAllEmails, enrichAllBatch, } from "./orchestrator";
|
|
48
48
|
export { getSmartLeadToken, getSmartLeadUser, clearSmartLeadToken, clearAllSmartLeadTokens, getSmartLeadTokenCacheStats, enableFileCache, disableFileCache, isFileCacheEnabled, clearFileCache, type SmartLeadCredentials, type SmartLeadAuthConfig, type SmartLeadUser, type SmartLeadLoginResponse, } from "./auth";
|
|
49
49
|
export { calculateMatchConfidence, classifyMatchQuality, findBestMatch, matchContacts, buildSmartProspectFiltersFromLinkedIn, parseLinkedInSearchResponse, enrichLinkedInContact, enrichLinkedInContactsBatch, createLinkedInEnricher, getEmailsForLinkedInContact, getEmailsForLinkedInContactsBatch, salesLeadToContact, type LinkedInContact, type MatchResult, type MatchOptions, type LinkedInEnrichmentResult, type LinkedInEnrichmentOptions, type EmailSource, type EmailResult, type GetEmailsResult, type GetEmailsConfig, type GetEmailsOptions, } from "./matching";
|
package/dist/enrichment/index.js
CHANGED
|
@@ -43,13 +43,14 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
43
43
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
44
44
|
};
|
|
45
45
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
-
exports.
|
|
47
|
-
exports.salesLeadToContact = exports.getEmailsForLinkedInContactsBatch = exports.getEmailsForLinkedInContact = exports.createLinkedInEnricher = exports.enrichLinkedInContactsBatch = exports.enrichLinkedInContact = exports.parseLinkedInSearchResponse = exports.buildSmartProspectFiltersFromLinkedIn = void 0;
|
|
46
|
+
exports.findBestMatch = exports.classifyMatchQuality = exports.calculateMatchConfidence = exports.clearFileCache = exports.isFileCacheEnabled = exports.disableFileCache = exports.enableFileCache = exports.getSmartLeadTokenCacheStats = exports.clearAllSmartLeadTokens = exports.clearSmartLeadToken = exports.getSmartLeadUser = exports.getSmartLeadToken = exports.enrichAllBatch = exports.enrichAllEmails = exports.enrichBatch = exports.enrichBusinessEmail = exports.createSmartProspectClient = exports.extractNumericLinkedInId = exports.clearSnovioTokenCache = exports.verifyEmailWithSnovio = exports.findEmailsWithSnovio = exports.verifyEmailsBatch = exports.checkCatchAllDomain = exports.verifyEmailWithBouncer = exports.createSnovioProvider = exports.createBouncerProvider = exports.createDropcontactProvider = exports.createHunterProvider = exports.createCosiallProvider = exports.createSmartProspectProvider = exports.createLddProvider = exports.createConstructProvider = exports.verifyEmailsExist = exports.checkDomainCatchAll = exports.verifyEmailMx = exports.extractLinkedInUsername = exports.hostnameFromUrl = exports.cleanNamePart = exports.asciiFold = exports.isRoleAccount = exports.isValidEmailSyntax = exports.DISPOSABLE_DOMAINS = exports.isDisposableDomain = exports.isDisposableEmail = exports.PERSONAL_DOMAINS = exports.isPersonalDomain = exports.isBusinessEmail = exports.isPersonalEmail = exports.DEFAULT_PROVIDER_ORDER = exports.PROVIDER_COSTS = void 0;
|
|
47
|
+
exports.salesLeadToContact = exports.getEmailsForLinkedInContactsBatch = exports.getEmailsForLinkedInContact = exports.createLinkedInEnricher = exports.enrichLinkedInContactsBatch = exports.enrichLinkedInContact = exports.parseLinkedInSearchResponse = exports.buildSmartProspectFiltersFromLinkedIn = exports.matchContacts = void 0;
|
|
48
48
|
exports.createEnrichmentClient = createEnrichmentClient;
|
|
49
49
|
const orchestrator_1 = require("./orchestrator");
|
|
50
50
|
const construct_1 = require("./providers/construct");
|
|
51
51
|
const ldd_1 = require("./providers/ldd");
|
|
52
52
|
const smartprospect_1 = require("./providers/smartprospect");
|
|
53
|
+
const cosiall_1 = require("./providers/cosiall");
|
|
53
54
|
const hunter_1 = require("./providers/hunter");
|
|
54
55
|
const dropcontact_1 = require("./providers/dropcontact");
|
|
55
56
|
const bouncer_1 = require("./providers/bouncer");
|
|
@@ -60,6 +61,7 @@ const snovio_1 = require("./providers/snovio");
|
|
|
60
61
|
* PHASE 1 - Free lookups (real data):
|
|
61
62
|
* - ldd: LinkedIn Data Dump - real verified emails (FREE)
|
|
62
63
|
* - smartprospect: SmartLead API - real verified emails (FREE with subscription)
|
|
64
|
+
* - cosiall: Cosiall Profile Emails - emails from LinkedIn profiles (FREE)
|
|
63
65
|
* - construct: Pattern guessing + MX check (FREE)
|
|
64
66
|
*
|
|
65
67
|
* PHASE 2 - Paid verification/finding (only if needed):
|
|
@@ -72,6 +74,7 @@ const snovio_1 = require("./providers/snovio");
|
|
|
72
74
|
const DEFAULT_ORDER = [
|
|
73
75
|
"ldd",
|
|
74
76
|
"smartprospect",
|
|
77
|
+
"cosiall",
|
|
75
78
|
"construct",
|
|
76
79
|
"bouncer",
|
|
77
80
|
"snovio",
|
|
@@ -96,6 +99,10 @@ function createEnrichmentClient(config) {
|
|
|
96
99
|
if (providerConfigs.smartprospect) {
|
|
97
100
|
providerFuncs.set("smartprospect", (0, smartprospect_1.createSmartProspectProvider)(providerConfigs.smartprospect));
|
|
98
101
|
}
|
|
102
|
+
// Cosiall is FREE - always create if not explicitly disabled
|
|
103
|
+
if (providerConfigs.cosiall?.enabled !== false) {
|
|
104
|
+
providerFuncs.set("cosiall", (0, cosiall_1.createCosiallProvider)(providerConfigs.cosiall));
|
|
105
|
+
}
|
|
99
106
|
if (providerConfigs.hunter) {
|
|
100
107
|
providerFuncs.set("hunter", (0, hunter_1.createHunterProvider)(providerConfigs.hunter));
|
|
101
108
|
}
|
|
@@ -270,6 +277,7 @@ var providers_1 = require("./providers");
|
|
|
270
277
|
Object.defineProperty(exports, "createConstructProvider", { enumerable: true, get: function () { return providers_1.createConstructProvider; } });
|
|
271
278
|
Object.defineProperty(exports, "createLddProvider", { enumerable: true, get: function () { return providers_1.createLddProvider; } });
|
|
272
279
|
Object.defineProperty(exports, "createSmartProspectProvider", { enumerable: true, get: function () { return providers_1.createSmartProspectProvider; } });
|
|
280
|
+
Object.defineProperty(exports, "createCosiallProvider", { enumerable: true, get: function () { return providers_1.createCosiallProvider; } });
|
|
273
281
|
Object.defineProperty(exports, "createHunterProvider", { enumerable: true, get: function () { return providers_1.createHunterProvider; } });
|
|
274
282
|
Object.defineProperty(exports, "createDropcontactProvider", { enumerable: true, get: function () { return providers_1.createDropcontactProvider; } });
|
|
275
283
|
Object.defineProperty(exports, "createBouncerProvider", { enumerable: true, get: function () { return providers_1.createBouncerProvider; } });
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* Matches contacts between LinkedIn Sales Navigator and SmartProspect
|
|
5
5
|
* to find the same person across both platforms.
|
|
6
6
|
*/
|
|
7
|
-
import type { SmartProspectContact, SmartProspectConfig, SmartProspectFetchResponse, LddConfig } from
|
|
8
|
-
import type { SalesLeadSearchResult } from
|
|
9
|
-
import { type SmartProspectClient } from
|
|
7
|
+
import type { SmartProspectContact, SmartProspectConfig, SmartProspectFetchResponse, LddConfig } from "./types";
|
|
8
|
+
import type { SalesLeadSearchResult } from "../types";
|
|
9
|
+
import { type SmartProspectClient } from "./providers/smartprospect";
|
|
10
10
|
/**
|
|
11
11
|
* LinkedIn Sales Navigator contact (simplified from API response)
|
|
12
12
|
*/
|
|
@@ -68,7 +68,7 @@ export interface MatchResult {
|
|
|
68
68
|
/** Which fields matched */
|
|
69
69
|
matchedFields: string[];
|
|
70
70
|
/** Match quality classification */
|
|
71
|
-
quality:
|
|
71
|
+
quality: "exact" | "high" | "medium" | "low" | "none";
|
|
72
72
|
}
|
|
73
73
|
/**
|
|
74
74
|
* Options for matching
|
|
@@ -91,7 +91,7 @@ export declare function calculateMatchConfidence(linkedin: LinkedInContact, smar
|
|
|
91
91
|
/**
|
|
92
92
|
* Classify match quality based on confidence and matched fields
|
|
93
93
|
*/
|
|
94
|
-
export declare function classifyMatchQuality(confidence: number, matchedFields: string[]):
|
|
94
|
+
export declare function classifyMatchQuality(confidence: number, matchedFields: string[]): "exact" | "high" | "medium" | "low" | "none";
|
|
95
95
|
/**
|
|
96
96
|
* Find the best matching SmartProspect contact for a LinkedIn contact
|
|
97
97
|
*/
|
|
@@ -130,7 +130,7 @@ export interface LinkedInEnrichmentResult {
|
|
|
130
130
|
/** Match confidence score (0-100) */
|
|
131
131
|
matchConfidence: number;
|
|
132
132
|
/** Match quality classification */
|
|
133
|
-
matchQuality:
|
|
133
|
+
matchQuality: "exact" | "high" | "medium" | "low" | "none";
|
|
134
134
|
/** Which fields matched */
|
|
135
135
|
matchedFields: string[];
|
|
136
136
|
/** Enriched email (if fetched) */
|
|
@@ -258,7 +258,7 @@ export declare function createLinkedInEnricher(smartProspectConfig: SmartProspec
|
|
|
258
258
|
/**
|
|
259
259
|
* Email source - where the email was found
|
|
260
260
|
*/
|
|
261
|
-
export type EmailSource =
|
|
261
|
+
export type EmailSource = "ldd" | "smartprospect" | "cosiall" | "linkedin" | "pattern" | "hunter" | "bouncer" | "snovio";
|
|
262
262
|
/**
|
|
263
263
|
* Email result from unified lookup
|
|
264
264
|
*/
|
|
@@ -270,7 +270,7 @@ export interface EmailResult {
|
|
|
270
270
|
/** Confidence score (0-100) */
|
|
271
271
|
confidence: number;
|
|
272
272
|
/** Email type classification */
|
|
273
|
-
type:
|
|
273
|
+
type: "business" | "personal" | "unknown";
|
|
274
274
|
/** Whether the email was verified */
|
|
275
275
|
verified: boolean;
|
|
276
276
|
/** Email deliverability score (0-1) for SmartProspect emails */
|
|
@@ -305,6 +305,10 @@ export interface GetEmailsConfig {
|
|
|
305
305
|
ldd?: LddConfig;
|
|
306
306
|
/** SmartProspect configuration (FREE for FlexIQ - already paying monthly) */
|
|
307
307
|
smartprospect?: SmartProspectConfig;
|
|
308
|
+
/** Cosiall Profile Emails (FREE - uses global Cosiall config, no config needed here) */
|
|
309
|
+
cosiall?: {
|
|
310
|
+
enabled?: boolean;
|
|
311
|
+
};
|
|
308
312
|
/** Company domain for email pattern guessing (optional - if not provided, will try to discover) */
|
|
309
313
|
companyDomain?: string;
|
|
310
314
|
/**
|
|
@@ -337,6 +341,8 @@ export interface GetEmailsOptions {
|
|
|
337
341
|
skipLdd?: boolean;
|
|
338
342
|
/** Skip SmartProspect lookup (default: false) */
|
|
339
343
|
skipSmartProspect?: boolean;
|
|
344
|
+
/** Skip Cosiall Profile Emails lookup (default: false) */
|
|
345
|
+
skipCosiall?: boolean;
|
|
340
346
|
/** Skip email pattern guessing (default: false) */
|
|
341
347
|
skipPatternGuessing?: boolean;
|
|
342
348
|
/** Skip LinkedIn company lookup for domain discovery (default: false) */
|
|
@@ -70,8 +70,8 @@ const ldd_1 = require("./providers/ldd");
|
|
|
70
70
|
function salesLeadToContact(lead) {
|
|
71
71
|
// Use firstName/lastName directly from Sales Nav - they're already separate fields
|
|
72
72
|
// This preserves exact names as shown on LinkedIn (important for SmartProspect matching)
|
|
73
|
-
const firstName = lead.firstName ||
|
|
74
|
-
const lastName = lead.lastName ||
|
|
73
|
+
const firstName = lead.firstName || "";
|
|
74
|
+
const lastName = lead.lastName || "";
|
|
75
75
|
return {
|
|
76
76
|
objectUrn: lead.objectUrn,
|
|
77
77
|
entityUrn: lead.salesProfileUrn,
|
|
@@ -100,7 +100,7 @@ function salesLeadToContact(lead) {
|
|
|
100
100
|
*/
|
|
101
101
|
function isSalesLeadSearchResult(input) {
|
|
102
102
|
// SalesLeadSearchResult has 'name' but not 'firstName'
|
|
103
|
-
return
|
|
103
|
+
return "name" in input && !("firstName" in input);
|
|
104
104
|
}
|
|
105
105
|
/**
|
|
106
106
|
* Normalize input to LinkedInContact - accepts either format
|
|
@@ -119,8 +119,8 @@ function normalizeToContact(input) {
|
|
|
119
119
|
*/
|
|
120
120
|
function normalize(str) {
|
|
121
121
|
if (!str)
|
|
122
|
-
return
|
|
123
|
-
return str.toLowerCase().trim().replace(/\s+/g,
|
|
122
|
+
return "";
|
|
123
|
+
return str.toLowerCase().trim().replace(/\s+/g, " ");
|
|
124
124
|
}
|
|
125
125
|
/**
|
|
126
126
|
* Extract company name from LinkedIn position
|
|
@@ -128,15 +128,15 @@ function normalize(str) {
|
|
|
128
128
|
function getLinkedInCompany(contact) {
|
|
129
129
|
const pos = contact.currentPositions?.[0];
|
|
130
130
|
if (!pos)
|
|
131
|
-
return
|
|
132
|
-
return pos.companyUrnResolutionResult?.name || pos.companyName ||
|
|
131
|
+
return "";
|
|
132
|
+
return pos.companyUrnResolutionResult?.name || pos.companyName || "";
|
|
133
133
|
}
|
|
134
134
|
/**
|
|
135
135
|
* Extract job title from LinkedIn position
|
|
136
136
|
*/
|
|
137
137
|
function getLinkedInTitle(contact) {
|
|
138
138
|
const pos = contact.currentPositions?.[0];
|
|
139
|
-
return pos?.title ||
|
|
139
|
+
return pos?.title || "";
|
|
140
140
|
}
|
|
141
141
|
/**
|
|
142
142
|
* Extract location parts from LinkedIn geoRegion
|
|
@@ -144,8 +144,8 @@ function getLinkedInTitle(contact) {
|
|
|
144
144
|
*/
|
|
145
145
|
function parseLinkedInLocation(geoRegion) {
|
|
146
146
|
if (!geoRegion)
|
|
147
|
-
return { city:
|
|
148
|
-
const parts = geoRegion.split(
|
|
147
|
+
return { city: "", state: "", country: "" };
|
|
148
|
+
const parts = geoRegion.split(",").map((p) => p.trim());
|
|
149
149
|
if (parts.length >= 3) {
|
|
150
150
|
return {
|
|
151
151
|
city: parts[0],
|
|
@@ -156,15 +156,15 @@ function parseLinkedInLocation(geoRegion) {
|
|
|
156
156
|
else if (parts.length === 2) {
|
|
157
157
|
return {
|
|
158
158
|
city: parts[0],
|
|
159
|
-
state:
|
|
159
|
+
state: "",
|
|
160
160
|
country: parts[1],
|
|
161
161
|
};
|
|
162
162
|
}
|
|
163
163
|
else {
|
|
164
164
|
return {
|
|
165
|
-
city:
|
|
166
|
-
state:
|
|
167
|
-
country: parts[0] ||
|
|
165
|
+
city: "",
|
|
166
|
+
state: "",
|
|
167
|
+
country: parts[0] || "",
|
|
168
168
|
};
|
|
169
169
|
}
|
|
170
170
|
}
|
|
@@ -209,7 +209,7 @@ function stringSimilarity(a, b) {
|
|
|
209
209
|
* Check if strings match exactly (normalized)
|
|
210
210
|
*/
|
|
211
211
|
function exactMatch(a, b) {
|
|
212
|
-
return normalize(a) === normalize(b) && normalize(a) !==
|
|
212
|
+
return normalize(a) === normalize(b) && normalize(a) !== "";
|
|
213
213
|
}
|
|
214
214
|
/**
|
|
215
215
|
* Check if strings match with fuzzy tolerance
|
|
@@ -245,50 +245,52 @@ function calculateMatchConfidence(linkedin, smartprospect, options = {}) {
|
|
|
245
245
|
// First name match
|
|
246
246
|
if (exactMatch(linkedin.firstName, smartprospect.firstName)) {
|
|
247
247
|
score += 20;
|
|
248
|
-
matchedFields.push(
|
|
248
|
+
matchedFields.push("firstName:exact");
|
|
249
249
|
}
|
|
250
|
-
else if (fuzzyNames &&
|
|
250
|
+
else if (fuzzyNames &&
|
|
251
|
+
fuzzyMatch(linkedin.firstName, smartprospect.firstName)) {
|
|
251
252
|
score += 15;
|
|
252
|
-
matchedFields.push(
|
|
253
|
+
matchedFields.push("firstName:fuzzy");
|
|
253
254
|
}
|
|
254
255
|
// Last name match
|
|
255
256
|
if (exactMatch(linkedin.lastName, smartprospect.lastName)) {
|
|
256
257
|
score += 20;
|
|
257
|
-
matchedFields.push(
|
|
258
|
+
matchedFields.push("lastName:exact");
|
|
258
259
|
}
|
|
259
|
-
else if (fuzzyNames &&
|
|
260
|
+
else if (fuzzyNames &&
|
|
261
|
+
fuzzyMatch(linkedin.lastName, smartprospect.lastName)) {
|
|
260
262
|
score += 15;
|
|
261
|
-
matchedFields.push(
|
|
263
|
+
matchedFields.push("lastName:fuzzy");
|
|
262
264
|
}
|
|
263
265
|
// === Company matching (up to 30 points) ===
|
|
264
266
|
const liCompany = getLinkedInCompany(linkedin);
|
|
265
|
-
const spCompany = smartprospect.company?.name ||
|
|
267
|
+
const spCompany = smartprospect.company?.name || "";
|
|
266
268
|
if (exactMatch(liCompany, spCompany)) {
|
|
267
269
|
score += 30;
|
|
268
|
-
matchedFields.push(
|
|
270
|
+
matchedFields.push("company:exact");
|
|
269
271
|
}
|
|
270
272
|
else if (fuzzyCompany && fuzzyMatch(liCompany, spCompany, 0.8)) {
|
|
271
273
|
score += 25;
|
|
272
|
-
matchedFields.push(
|
|
274
|
+
matchedFields.push("company:fuzzy");
|
|
273
275
|
}
|
|
274
276
|
else if (containsMatch(liCompany, spCompany)) {
|
|
275
277
|
score += 15;
|
|
276
|
-
matchedFields.push(
|
|
278
|
+
matchedFields.push("company:contains");
|
|
277
279
|
}
|
|
278
280
|
// === Title matching (up to 15 points) ===
|
|
279
281
|
const liTitle = getLinkedInTitle(linkedin);
|
|
280
|
-
const spTitle = smartprospect.title ||
|
|
282
|
+
const spTitle = smartprospect.title || "";
|
|
281
283
|
if (exactMatch(liTitle, spTitle)) {
|
|
282
284
|
score += 15;
|
|
283
|
-
matchedFields.push(
|
|
285
|
+
matchedFields.push("title:exact");
|
|
284
286
|
}
|
|
285
287
|
else if (fuzzyMatch(liTitle, spTitle, 0.75)) {
|
|
286
288
|
score += 12;
|
|
287
|
-
matchedFields.push(
|
|
289
|
+
matchedFields.push("title:fuzzy");
|
|
288
290
|
}
|
|
289
291
|
else if (containsMatch(liTitle, spTitle)) {
|
|
290
292
|
score += 8;
|
|
291
|
-
matchedFields.push(
|
|
293
|
+
matchedFields.push("title:contains");
|
|
292
294
|
}
|
|
293
295
|
// === Location matching (up to 15 points) ===
|
|
294
296
|
const liLocation = parseLinkedInLocation(linkedin.geoRegion);
|
|
@@ -298,17 +300,17 @@ function calculateMatchConfidence(linkedin, smartprospect, options = {}) {
|
|
|
298
300
|
// Country match
|
|
299
301
|
if (spCountry && exactMatch(liLocation.country, smartprospect.country)) {
|
|
300
302
|
score += 5;
|
|
301
|
-
matchedFields.push(
|
|
303
|
+
matchedFields.push("country:exact");
|
|
302
304
|
}
|
|
303
305
|
// State match
|
|
304
306
|
if (spState && exactMatch(liLocation.state, smartprospect.state)) {
|
|
305
307
|
score += 5;
|
|
306
|
-
matchedFields.push(
|
|
308
|
+
matchedFields.push("state:exact");
|
|
307
309
|
}
|
|
308
310
|
// City match
|
|
309
311
|
if (spCity && exactMatch(liLocation.city, smartprospect.city)) {
|
|
310
312
|
score += 5;
|
|
311
|
-
matchedFields.push(
|
|
313
|
+
matchedFields.push("city:exact");
|
|
312
314
|
}
|
|
313
315
|
return { confidence: Math.min(100, score), matchedFields };
|
|
314
316
|
}
|
|
@@ -316,22 +318,22 @@ function calculateMatchConfidence(linkedin, smartprospect, options = {}) {
|
|
|
316
318
|
* Classify match quality based on confidence and matched fields
|
|
317
319
|
*/
|
|
318
320
|
function classifyMatchQuality(confidence, matchedFields) {
|
|
319
|
-
const hasNameMatch = matchedFields.some((f) => f.startsWith(
|
|
320
|
-
matchedFields.some((f) => f.startsWith(
|
|
321
|
-
const hasCompanyMatch = matchedFields.some((f) => f.startsWith(
|
|
321
|
+
const hasNameMatch = matchedFields.some((f) => f.startsWith("firstName:exact")) &&
|
|
322
|
+
matchedFields.some((f) => f.startsWith("lastName:exact"));
|
|
323
|
+
const hasCompanyMatch = matchedFields.some((f) => f.startsWith("company:"));
|
|
322
324
|
if (confidence >= 85 && hasNameMatch && hasCompanyMatch) {
|
|
323
|
-
return
|
|
325
|
+
return "exact";
|
|
324
326
|
}
|
|
325
327
|
else if (confidence >= 70 && hasNameMatch) {
|
|
326
|
-
return
|
|
328
|
+
return "high";
|
|
327
329
|
}
|
|
328
330
|
else if (confidence >= 50) {
|
|
329
|
-
return
|
|
331
|
+
return "medium";
|
|
330
332
|
}
|
|
331
333
|
else if (confidence >= 30) {
|
|
332
|
-
return
|
|
334
|
+
return "low";
|
|
333
335
|
}
|
|
334
|
-
return
|
|
336
|
+
return "none";
|
|
335
337
|
}
|
|
336
338
|
/**
|
|
337
339
|
* Find the best matching SmartProspect contact for a LinkedIn contact
|
|
@@ -399,8 +401,8 @@ function parseLinkedInSearchResponse(elements) {
|
|
|
399
401
|
return elements.map((el) => ({
|
|
400
402
|
objectUrn: el.objectUrn,
|
|
401
403
|
entityUrn: el.entityUrn,
|
|
402
|
-
firstName: el.firstName ||
|
|
403
|
-
lastName: el.lastName ||
|
|
404
|
+
firstName: el.firstName || "",
|
|
405
|
+
lastName: el.lastName || "",
|
|
404
406
|
fullName: el.fullName || undefined,
|
|
405
407
|
geoRegion: el.geoRegion || undefined,
|
|
406
408
|
currentPositions: el.currentPositions,
|
|
@@ -457,7 +459,7 @@ async function enrichLinkedInContact(linkedInContact, smartProspectConfig, optio
|
|
|
457
459
|
numericLinkedInId,
|
|
458
460
|
matchedContact: null,
|
|
459
461
|
matchConfidence: 0,
|
|
460
|
-
matchQuality:
|
|
462
|
+
matchQuality: "none",
|
|
461
463
|
matchedFields: [],
|
|
462
464
|
email: null,
|
|
463
465
|
emailDeliverability: 0,
|
|
@@ -468,7 +470,7 @@ async function enrichLinkedInContact(linkedInContact, smartProspectConfig, optio
|
|
|
468
470
|
// Create SmartProspect client
|
|
469
471
|
const client = (0, smartprospect_1.createSmartProspectClient)(smartProspectConfig);
|
|
470
472
|
if (!client) {
|
|
471
|
-
result.error =
|
|
473
|
+
result.error = "Failed to create SmartProspect client - check credentials";
|
|
472
474
|
return result;
|
|
473
475
|
}
|
|
474
476
|
try {
|
|
@@ -509,12 +511,16 @@ async function enrichLinkedInContact(linkedInContact, smartProspectConfig, optio
|
|
|
509
511
|
}
|
|
510
512
|
}
|
|
511
513
|
if (result.allCandidates.length === 0) {
|
|
512
|
-
result.error =
|
|
514
|
+
result.error = "No candidates found in SmartProspect";
|
|
513
515
|
return result;
|
|
514
516
|
}
|
|
515
517
|
}
|
|
516
518
|
// Step 2: Find best match using intelligent matching
|
|
517
|
-
const matchResult = findBestMatch(linkedInContact, result.allCandidates, {
|
|
519
|
+
const matchResult = findBestMatch(linkedInContact, result.allCandidates, {
|
|
520
|
+
minConfidence,
|
|
521
|
+
fuzzyNames,
|
|
522
|
+
fuzzyCompany,
|
|
523
|
+
});
|
|
518
524
|
if (!matchResult) {
|
|
519
525
|
result.error = `No match above ${minConfidence}% confidence threshold`;
|
|
520
526
|
return result;
|
|
@@ -525,7 +531,9 @@ async function enrichLinkedInContact(linkedInContact, smartProspectConfig, optio
|
|
|
525
531
|
result.matchedFields = matchResult.matchedFields;
|
|
526
532
|
// Step 3: Fetch email if auto-fetch enabled and good match (COSTS CREDITS)
|
|
527
533
|
if (autoFetch && matchResult.confidence >= minConfidence) {
|
|
528
|
-
const fetchResponse = await client.fetch([
|
|
534
|
+
const fetchResponse = await client.fetch([
|
|
535
|
+
matchResult.smartProspectContact.id,
|
|
536
|
+
]);
|
|
529
537
|
if (fetchResponse.success && fetchResponse.data.list.length > 0) {
|
|
530
538
|
const enrichedContact = fetchResponse.data.list[0];
|
|
531
539
|
result.email = enrichedContact.email || null;
|
|
@@ -539,7 +547,7 @@ async function enrichLinkedInContact(linkedInContact, smartProspectConfig, optio
|
|
|
539
547
|
return result;
|
|
540
548
|
}
|
|
541
549
|
catch (err) {
|
|
542
|
-
result.error = err instanceof Error ? err.message :
|
|
550
|
+
result.error = err instanceof Error ? err.message : "Unknown error";
|
|
543
551
|
return result;
|
|
544
552
|
}
|
|
545
553
|
}
|
|
@@ -604,7 +612,7 @@ function createLinkedInEnricher(smartProspectConfig, defaultOptions = {}) {
|
|
|
604
612
|
numericLinkedInId,
|
|
605
613
|
matchedContact: null,
|
|
606
614
|
matchConfidence: 0,
|
|
607
|
-
matchQuality:
|
|
615
|
+
matchQuality: "none",
|
|
608
616
|
matchedFields: [],
|
|
609
617
|
email: null,
|
|
610
618
|
emailDeliverability: 0,
|
|
@@ -634,7 +642,9 @@ function createLinkedInEnricher(smartProspectConfig, defaultOptions = {}) {
|
|
|
634
642
|
result.allCandidates = searchResponse.data.list;
|
|
635
643
|
result.totalCandidatesFound = searchResponse.data.total_count;
|
|
636
644
|
// Broader search fallback
|
|
637
|
-
if (searchResponse.data.list.length === 0 &&
|
|
645
|
+
if (searchResponse.data.list.length === 0 &&
|
|
646
|
+
includeCompany &&
|
|
647
|
+
filters.companyName) {
|
|
638
648
|
const broaderResponse = await client.search({
|
|
639
649
|
firstName: filters.firstName,
|
|
640
650
|
lastName: filters.lastName,
|
|
@@ -646,7 +656,7 @@ function createLinkedInEnricher(smartProspectConfig, defaultOptions = {}) {
|
|
|
646
656
|
}
|
|
647
657
|
}
|
|
648
658
|
if (result.allCandidates.length === 0) {
|
|
649
|
-
result.error =
|
|
659
|
+
result.error = "No candidates found in SmartProspect";
|
|
650
660
|
return result;
|
|
651
661
|
}
|
|
652
662
|
const matchResult = findBestMatch(contact, result.allCandidates, {
|
|
@@ -663,12 +673,16 @@ function createLinkedInEnricher(smartProspectConfig, defaultOptions = {}) {
|
|
|
663
673
|
result.matchQuality = matchResult.quality;
|
|
664
674
|
result.matchedFields = matchResult.matchedFields;
|
|
665
675
|
if (autoFetch && matchResult.confidence >= minConfidence) {
|
|
666
|
-
const fetchResponse = await client.fetch([
|
|
676
|
+
const fetchResponse = await client.fetch([
|
|
677
|
+
matchResult.smartProspectContact.id,
|
|
678
|
+
]);
|
|
667
679
|
if (fetchResponse.success && fetchResponse.data.list.length > 0) {
|
|
668
680
|
const enrichedContact = fetchResponse.data.list[0];
|
|
669
681
|
result.email = enrichedContact.email || null;
|
|
670
|
-
result.emailDeliverability =
|
|
671
|
-
|
|
682
|
+
result.emailDeliverability =
|
|
683
|
+
enrichedContact.emailDeliverability || 0;
|
|
684
|
+
result.verificationStatus =
|
|
685
|
+
enrichedContact.verificationStatus || null;
|
|
672
686
|
result.matchedContact = enrichedContact;
|
|
673
687
|
}
|
|
674
688
|
}
|
|
@@ -676,7 +690,7 @@ function createLinkedInEnricher(smartProspectConfig, defaultOptions = {}) {
|
|
|
676
690
|
return result;
|
|
677
691
|
}
|
|
678
692
|
catch (err) {
|
|
679
|
-
result.error = err instanceof Error ? err.message :
|
|
693
|
+
result.error = err instanceof Error ? err.message : "Unknown error";
|
|
680
694
|
return result;
|
|
681
695
|
}
|
|
682
696
|
},
|
|
@@ -766,7 +780,7 @@ function createLinkedInEnricher(smartProspectConfig, defaultOptions = {}) {
|
|
|
766
780
|
async function getEmailsForLinkedInContact(contactOrLead, config, options = {}) {
|
|
767
781
|
// Normalize input - accept either LinkedInContact or raw SalesLeadSearchResult
|
|
768
782
|
const contact = normalizeToContact(contactOrLead);
|
|
769
|
-
const { skipLdd = false, skipSmartProspect = false, skipPatternGuessing = false, skipPaidProviders = false, minMatchConfidence = 60, paidProviderThreshold = 80, includeCompany = true, } = options;
|
|
783
|
+
const { skipLdd = false, skipSmartProspect = false, skipCosiall = false, skipPatternGuessing = false, skipPaidProviders = false, minMatchConfidence = 60, paidProviderThreshold = 80, includeCompany = true, } = options;
|
|
770
784
|
// Extract numeric ID from objectUrn
|
|
771
785
|
const numericLinkedInId = (0, ldd_1.extractNumericLinkedInId)(contact.objectUrn);
|
|
772
786
|
// Extract company domain from contact (LinkedIn data - often missing)
|
|
@@ -790,30 +804,34 @@ async function getEmailsForLinkedInContact(contactOrLead, config, options = {})
|
|
|
790
804
|
// Track company domain discovered from SmartProspect (since LinkedIn doesn't provide it)
|
|
791
805
|
let discoveredCompanyDomain = null;
|
|
792
806
|
// ==========================================================================
|
|
793
|
-
// Phase 1: FREE providers in PARALLEL (LDD + SmartProspect)
|
|
807
|
+
// Phase 1: FREE providers in PARALLEL (LDD + SmartProspect + Cosiall)
|
|
794
808
|
// ==========================================================================
|
|
795
809
|
const freeProviderPromises = [];
|
|
796
810
|
// LDD lookup (FREE)
|
|
797
811
|
if (!skipLdd && config.ldd?.apiUrl && config.ldd?.apiToken) {
|
|
798
|
-
result.providersQueried.push(
|
|
812
|
+
result.providersQueried.push("ldd");
|
|
799
813
|
freeProviderPromises.push(queryLdd(contact, config.ldd, numericLinkedInId, addEmail, result));
|
|
800
814
|
}
|
|
801
815
|
// SmartProspect lookup (FREE for FlexIQ)
|
|
802
816
|
// This also extracts company domain for pattern guessing
|
|
803
817
|
if (!skipSmartProspect && config.smartprospect) {
|
|
804
|
-
result.providersQueried.push(
|
|
805
|
-
freeProviderPromises.push(querySmartProspect(contact, config.smartprospect, minMatchConfidence, includeCompany, addEmail, result)
|
|
806
|
-
.then((domain) => {
|
|
818
|
+
result.providersQueried.push("smartprospect");
|
|
819
|
+
freeProviderPromises.push(querySmartProspect(contact, config.smartprospect, minMatchConfidence, includeCompany, addEmail, result).then((domain) => {
|
|
807
820
|
if (domain) {
|
|
808
821
|
discoveredCompanyDomain = domain;
|
|
809
822
|
}
|
|
810
823
|
}));
|
|
811
824
|
}
|
|
825
|
+
// Cosiall Profile Emails lookup (FREE)
|
|
826
|
+
if (!skipCosiall && config.cosiall?.enabled !== false) {
|
|
827
|
+
result.providersQueried.push("cosiall");
|
|
828
|
+
freeProviderPromises.push(queryCosiall(contact, addEmail, result));
|
|
829
|
+
}
|
|
812
830
|
// Wait for both free providers
|
|
813
831
|
await Promise.all(freeProviderPromises);
|
|
814
832
|
// Check if we have good enough results already
|
|
815
833
|
const bestConfidenceAfterFreeProviders = result.emails.length > 0
|
|
816
|
-
? Math.max(...result.emails.map(e => e.confidence))
|
|
834
|
+
? Math.max(...result.emails.map((e) => e.confidence))
|
|
817
835
|
: 0;
|
|
818
836
|
// ==========================================================================
|
|
819
837
|
// Phase 2: Domain Discovery (if needed for pattern guessing)
|
|
@@ -831,7 +849,7 @@ async function getEmailsForLinkedInContact(contactOrLead, config, options = {})
|
|
|
831
849
|
if (companyUrn) {
|
|
832
850
|
const companyId = extractCompanyIdFromUrn(companyUrn);
|
|
833
851
|
if (companyId) {
|
|
834
|
-
result.providersQueried.push(
|
|
852
|
+
result.providersQueried.push("linkedin");
|
|
835
853
|
try {
|
|
836
854
|
const company = await config.linkedInCompanyLookup(companyId);
|
|
837
855
|
if (company?.websiteUrl) {
|
|
@@ -842,7 +860,7 @@ async function getEmailsForLinkedInContact(contactOrLead, config, options = {})
|
|
|
842
860
|
}
|
|
843
861
|
}
|
|
844
862
|
catch (err) {
|
|
845
|
-
result.errors?.push(`LinkedIn: ${err instanceof Error ? err.message :
|
|
863
|
+
result.errors?.push(`LinkedIn: ${err instanceof Error ? err.message : "Company lookup failed"}`);
|
|
846
864
|
}
|
|
847
865
|
}
|
|
848
866
|
}
|
|
@@ -854,13 +872,15 @@ async function getEmailsForLinkedInContact(contactOrLead, config, options = {})
|
|
|
854
872
|
// ==========================================================================
|
|
855
873
|
// Phase 3: Email pattern guessing with MX verification (FREE)
|
|
856
874
|
// ==========================================================================
|
|
857
|
-
if (!skipPatternGuessing &&
|
|
858
|
-
|
|
875
|
+
if (!skipPatternGuessing &&
|
|
876
|
+
companyDomain &&
|
|
877
|
+
bestConfidenceAfterFreeProviders < paidProviderThreshold) {
|
|
878
|
+
result.providersQueried.push("pattern");
|
|
859
879
|
await queryPatternGuessing(contact, companyDomain, addEmail, result);
|
|
860
880
|
}
|
|
861
881
|
// Recalculate best confidence
|
|
862
882
|
const finalBestConfidence = result.emails.length > 0
|
|
863
|
-
? Math.max(...result.emails.map(e => e.confidence))
|
|
883
|
+
? Math.max(...result.emails.map((e) => e.confidence))
|
|
864
884
|
: 0;
|
|
865
885
|
// ==========================================================================
|
|
866
886
|
// Phase 3: PAID providers as last resort (Hunter/Bouncer/Snovio)
|
|
@@ -869,7 +889,9 @@ async function getEmailsForLinkedInContact(contactOrLead, config, options = {})
|
|
|
869
889
|
// Only use paid providers if we have low confidence or no results
|
|
870
890
|
// TODO: Implement Hunter, Bouncer, Snovio providers when needed
|
|
871
891
|
// For now, just mark that we would have queried them
|
|
872
|
-
if (config.hunter?.apiKey ||
|
|
892
|
+
if (config.hunter?.apiKey ||
|
|
893
|
+
config.bouncer?.apiKey ||
|
|
894
|
+
config.snovio?.userId) {
|
|
873
895
|
// result.providersQueried.push('hunter');
|
|
874
896
|
// result.providersQueried.push('bouncer');
|
|
875
897
|
// result.providersQueried.push('snovio');
|
|
@@ -880,11 +902,12 @@ async function getEmailsForLinkedInContact(contactOrLead, config, options = {})
|
|
|
880
902
|
const sourcePriority = {
|
|
881
903
|
ldd: 0, // Highest priority - your own data
|
|
882
904
|
smartprospect: 1,
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
905
|
+
cosiall: 2, // Cosiall Profile Emails (FREE)
|
|
906
|
+
linkedin: 3, // LinkedIn company lookup (for domain discovery, doesn't provide emails)
|
|
907
|
+
pattern: 4,
|
|
908
|
+
hunter: 5,
|
|
909
|
+
bouncer: 6,
|
|
910
|
+
snovio: 7,
|
|
888
911
|
};
|
|
889
912
|
result.emails.sort((a, b) => {
|
|
890
913
|
if (b.confidence !== a.confidence) {
|
|
@@ -920,12 +943,15 @@ function extractDomainFromUrl(url) {
|
|
|
920
943
|
if (!url)
|
|
921
944
|
return null;
|
|
922
945
|
try {
|
|
923
|
-
const fullUrl = url.startsWith(
|
|
924
|
-
return new URL(fullUrl).hostname.replace(/^www\./,
|
|
946
|
+
const fullUrl = url.startsWith("http") ? url : `https://${url}`;
|
|
947
|
+
return new URL(fullUrl).hostname.replace(/^www\./, "").toLowerCase();
|
|
925
948
|
}
|
|
926
949
|
catch {
|
|
927
950
|
// If URL parsing fails, try simple extraction
|
|
928
|
-
return url
|
|
951
|
+
return (url
|
|
952
|
+
.replace(/^(https?:\/\/)?(www\.)?/, "")
|
|
953
|
+
.split("/")[0]
|
|
954
|
+
.toLowerCase() || null);
|
|
929
955
|
}
|
|
930
956
|
}
|
|
931
957
|
/**
|
|
@@ -950,13 +976,13 @@ async function queryLdd(contact, lddConfig, numericLinkedInId, addEmail, result)
|
|
|
950
976
|
firstName: contact.firstName,
|
|
951
977
|
lastName: contact.lastName,
|
|
952
978
|
});
|
|
953
|
-
if (lddResult &&
|
|
979
|
+
if (lddResult && "emails" in lddResult && lddResult.emails.length > 0) {
|
|
954
980
|
for (const emailData of lddResult.emails) {
|
|
955
981
|
addEmail({
|
|
956
982
|
email: emailData.email,
|
|
957
|
-
source:
|
|
983
|
+
source: "ldd",
|
|
958
984
|
confidence: emailData.confidence ?? 90,
|
|
959
|
-
type: emailData.metadata?.emailTypeClassified ||
|
|
985
|
+
type: emailData.metadata?.emailTypeClassified || "unknown",
|
|
960
986
|
verified: emailData.verified ?? true,
|
|
961
987
|
metadata: emailData.metadata,
|
|
962
988
|
});
|
|
@@ -964,7 +990,55 @@ async function queryLdd(contact, lddConfig, numericLinkedInId, addEmail, result)
|
|
|
964
990
|
}
|
|
965
991
|
}
|
|
966
992
|
catch (err) {
|
|
967
|
-
result.errors?.push(`LDD: ${err instanceof Error ? err.message :
|
|
993
|
+
result.errors?.push(`LDD: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Query Cosiall Profile Emails provider
|
|
998
|
+
*/
|
|
999
|
+
async function queryCosiall(contact, addEmail, result) {
|
|
1000
|
+
try {
|
|
1001
|
+
// Import dynamically to avoid circular deps
|
|
1002
|
+
const { fetchProfileEmailsFromCosiall } = await Promise.resolve().then(() => __importStar(require("../cosiall-client")));
|
|
1003
|
+
// Build lookup parameters from contact
|
|
1004
|
+
const objectUrn = contact.objectUrn;
|
|
1005
|
+
const linkedInUrl = contact.entityUrn
|
|
1006
|
+
? undefined // entityUrn is not a URL
|
|
1007
|
+
: undefined;
|
|
1008
|
+
// Extract vanity from contact if available
|
|
1009
|
+
// (LinkedInContact doesn't have direct username field, but could be derived)
|
|
1010
|
+
// Must have at least objectUrn to query Cosiall
|
|
1011
|
+
if (!objectUrn) {
|
|
1012
|
+
return; // Silently skip - no identifier available
|
|
1013
|
+
}
|
|
1014
|
+
const cosiallResult = await fetchProfileEmailsFromCosiall({
|
|
1015
|
+
objectUrn,
|
|
1016
|
+
linkedInUrl,
|
|
1017
|
+
});
|
|
1018
|
+
if (cosiallResult.emails && cosiallResult.emails.length > 0) {
|
|
1019
|
+
for (const email of cosiallResult.emails) {
|
|
1020
|
+
addEmail({
|
|
1021
|
+
email,
|
|
1022
|
+
source: "cosiall",
|
|
1023
|
+
confidence: 85, // Good confidence - profile-associated emails
|
|
1024
|
+
type: "unknown", // Cosiall doesn't classify email type
|
|
1025
|
+
verified: true, // These are from LinkedIn profiles
|
|
1026
|
+
metadata: {
|
|
1027
|
+
profileId: cosiallResult.profileId,
|
|
1028
|
+
objectUrn: cosiallResult.objectUrn,
|
|
1029
|
+
linkedInUrl: cosiallResult.linkedInUrl,
|
|
1030
|
+
},
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
catch (err) {
|
|
1036
|
+
// Silently fail for NOT_FOUND - just means profile not in database
|
|
1037
|
+
const errorMessage = err instanceof Error ? err.message : "Unknown error";
|
|
1038
|
+
if (!errorMessage.includes("not found") &&
|
|
1039
|
+
!errorMessage.includes("NOT_FOUND")) {
|
|
1040
|
+
result.errors?.push(`Cosiall: ${errorMessage}`);
|
|
1041
|
+
}
|
|
968
1042
|
}
|
|
969
1043
|
}
|
|
970
1044
|
/**
|
|
@@ -975,7 +1049,7 @@ async function querySmartProspect(contact, smartProspectConfig, minMatchConfiden
|
|
|
975
1049
|
try {
|
|
976
1050
|
const client = (0, smartprospect_1.createSmartProspectClient)(smartProspectConfig);
|
|
977
1051
|
if (!client) {
|
|
978
|
-
result.errors?.push(
|
|
1052
|
+
result.errors?.push("SmartProspect: Failed to create client");
|
|
979
1053
|
return null;
|
|
980
1054
|
}
|
|
981
1055
|
// Build search filters
|
|
@@ -1021,21 +1095,23 @@ async function querySmartProspect(contact, smartProspectConfig, minMatchConfiden
|
|
|
1021
1095
|
if (companyWebsite) {
|
|
1022
1096
|
// Clean up the domain (remove protocol, www, trailing slashes)
|
|
1023
1097
|
discoveredDomain = companyWebsite
|
|
1024
|
-
.replace(/^(https?:\/\/)?(www\.)?/,
|
|
1025
|
-
.split(
|
|
1098
|
+
.replace(/^(https?:\/\/)?(www\.)?/, "")
|
|
1099
|
+
.split("/")[0]
|
|
1026
1100
|
.toLowerCase();
|
|
1027
1101
|
}
|
|
1028
1102
|
// Fetch email for matched contact
|
|
1029
|
-
const fetchResponse = await client.fetch([
|
|
1103
|
+
const fetchResponse = await client.fetch([
|
|
1104
|
+
matchResult.smartProspectContact.id,
|
|
1105
|
+
]);
|
|
1030
1106
|
if (fetchResponse.success && fetchResponse.data.list.length > 0) {
|
|
1031
1107
|
const enrichedContact = fetchResponse.data.list[0];
|
|
1032
1108
|
if (enrichedContact.email) {
|
|
1033
1109
|
addEmail({
|
|
1034
1110
|
email: enrichedContact.email,
|
|
1035
|
-
source:
|
|
1111
|
+
source: "smartprospect",
|
|
1036
1112
|
confidence: matchResult.confidence,
|
|
1037
|
-
type:
|
|
1038
|
-
verified: enrichedContact.verificationStatus ===
|
|
1113
|
+
type: "business",
|
|
1114
|
+
verified: enrichedContact.verificationStatus === "verified",
|
|
1039
1115
|
deliverability: enrichedContact.emailDeliverability,
|
|
1040
1116
|
metadata: {
|
|
1041
1117
|
matchQuality: matchResult.quality,
|
|
@@ -1051,7 +1127,7 @@ async function querySmartProspect(contact, smartProspectConfig, minMatchConfiden
|
|
|
1051
1127
|
return discoveredDomain;
|
|
1052
1128
|
}
|
|
1053
1129
|
catch (err) {
|
|
1054
|
-
result.errors?.push(`SmartProspect: ${err instanceof Error ? err.message :
|
|
1130
|
+
result.errors?.push(`SmartProspect: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
1055
1131
|
return null;
|
|
1056
1132
|
}
|
|
1057
1133
|
}
|
|
@@ -1061,20 +1137,25 @@ async function querySmartProspect(contact, smartProspectConfig, minMatchConfiden
|
|
|
1061
1137
|
async function queryPatternGuessing(contact, companyDomain, addEmail, result) {
|
|
1062
1138
|
try {
|
|
1063
1139
|
// Import construct provider dynamically to avoid circular deps
|
|
1064
|
-
const { createConstructProvider } = await Promise.resolve().then(() => __importStar(require(
|
|
1065
|
-
const constructProvider = createConstructProvider({
|
|
1140
|
+
const { createConstructProvider } = await Promise.resolve().then(() => __importStar(require("./providers/construct")));
|
|
1141
|
+
const constructProvider = createConstructProvider({
|
|
1142
|
+
maxAttempts: 12,
|
|
1143
|
+
timeoutMs: 3000,
|
|
1144
|
+
});
|
|
1066
1145
|
const constructResult = await constructProvider({
|
|
1067
1146
|
firstName: contact.firstName,
|
|
1068
1147
|
lastName: contact.lastName,
|
|
1069
1148
|
domain: companyDomain,
|
|
1070
1149
|
});
|
|
1071
|
-
if (constructResult &&
|
|
1150
|
+
if (constructResult &&
|
|
1151
|
+
"emails" in constructResult &&
|
|
1152
|
+
constructResult.emails.length > 0) {
|
|
1072
1153
|
for (const emailData of constructResult.emails) {
|
|
1073
1154
|
addEmail({
|
|
1074
1155
|
email: emailData.email,
|
|
1075
|
-
source:
|
|
1156
|
+
source: "pattern",
|
|
1076
1157
|
confidence: emailData.confidence ?? 50,
|
|
1077
|
-
type:
|
|
1158
|
+
type: "business",
|
|
1078
1159
|
verified: emailData.verified ?? false,
|
|
1079
1160
|
isCatchAll: emailData.isCatchAll,
|
|
1080
1161
|
metadata: emailData.metadata,
|
|
@@ -1083,7 +1164,7 @@ async function queryPatternGuessing(contact, companyDomain, addEmail, result) {
|
|
|
1083
1164
|
}
|
|
1084
1165
|
}
|
|
1085
1166
|
catch (err) {
|
|
1086
|
-
result.errors?.push(`Pattern: ${err instanceof Error ? err.message :
|
|
1167
|
+
result.errors?.push(`Pattern: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
1087
1168
|
}
|
|
1088
1169
|
}
|
|
1089
1170
|
/**
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cosiall Profile Emails Provider
|
|
3
|
+
*
|
|
4
|
+
* Lookups against the Cosiall FlexIQ Profile Emails API.
|
|
5
|
+
* This is FREE and returns all known emails for a LinkedIn profile.
|
|
6
|
+
*
|
|
7
|
+
* Lookup priority:
|
|
8
|
+
* 1. objectUrn (most precise - "urn:li:fsd_profile:ACoAABcdEfG")
|
|
9
|
+
* 2. linkedInUrl (URL like "https://www.linkedin.com/in/john-doe/")
|
|
10
|
+
* 3. vanity (username extracted from URL or direct field)
|
|
11
|
+
*/
|
|
12
|
+
import type { EnrichmentCandidate, ProviderMultiResult, ProviderResult } from "../types";
|
|
13
|
+
/**
|
|
14
|
+
* Cosiall provider configuration
|
|
15
|
+
* No configuration needed - uses Cosiall API credentials from global config
|
|
16
|
+
*/
|
|
17
|
+
export interface CosiallConfig {
|
|
18
|
+
/** Whether to enable the provider (default: true) */
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Create the Cosiall provider function
|
|
23
|
+
*
|
|
24
|
+
* Returns all emails found for a LinkedIn profile.
|
|
25
|
+
* Since this is a free service, it should always be executed.
|
|
26
|
+
*/
|
|
27
|
+
export declare function createCosiallProvider(config?: CosiallConfig): (candidate: EnrichmentCandidate) => Promise<ProviderResult | ProviderMultiResult | null>;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Cosiall Profile Emails Provider
|
|
4
|
+
*
|
|
5
|
+
* Lookups against the Cosiall FlexIQ Profile Emails API.
|
|
6
|
+
* This is FREE and returns all known emails for a LinkedIn profile.
|
|
7
|
+
*
|
|
8
|
+
* Lookup priority:
|
|
9
|
+
* 1. objectUrn (most precise - "urn:li:fsd_profile:ACoAABcdEfG")
|
|
10
|
+
* 2. linkedInUrl (URL like "https://www.linkedin.com/in/john-doe/")
|
|
11
|
+
* 3. vanity (username extracted from URL or direct field)
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.createCosiallProvider = createCosiallProvider;
|
|
15
|
+
const cosiall_client_1 = require("../../cosiall-client");
|
|
16
|
+
const validation_1 = require("../utils/validation");
|
|
17
|
+
/**
|
|
18
|
+
* Extract objectUrn from candidate
|
|
19
|
+
*/
|
|
20
|
+
function extractObjectUrn(candidate) {
|
|
21
|
+
const objectUrn = candidate.objectUrn || candidate.object_urn;
|
|
22
|
+
if (objectUrn && objectUrn.startsWith("urn:li:")) {
|
|
23
|
+
return objectUrn;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Extract LinkedIn URL from candidate
|
|
29
|
+
*/
|
|
30
|
+
function extractLinkedInUrl(candidate) {
|
|
31
|
+
const url = candidate.linkedinUrl || candidate.linkedin_url;
|
|
32
|
+
if (url && url.includes("linkedin.com")) {
|
|
33
|
+
return url;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Extract vanity (username) from candidate
|
|
39
|
+
*/
|
|
40
|
+
function extractVanity(candidate) {
|
|
41
|
+
// Direct username field
|
|
42
|
+
const directUsername = candidate.linkedinUsername || candidate.linkedin_username;
|
|
43
|
+
if (directUsername) {
|
|
44
|
+
return directUsername;
|
|
45
|
+
}
|
|
46
|
+
// Extract from URL
|
|
47
|
+
const url = candidate.linkedinUrl || candidate.linkedin_url;
|
|
48
|
+
if (url) {
|
|
49
|
+
const extracted = (0, validation_1.extractLinkedInUsername)(url);
|
|
50
|
+
if (extracted) {
|
|
51
|
+
return extracted;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Create the Cosiall provider function
|
|
58
|
+
*
|
|
59
|
+
* Returns all emails found for a LinkedIn profile.
|
|
60
|
+
* Since this is a free service, it should always be executed.
|
|
61
|
+
*/
|
|
62
|
+
function createCosiallProvider(config) {
|
|
63
|
+
// Check if explicitly disabled
|
|
64
|
+
if (config?.enabled === false) {
|
|
65
|
+
const noopProvider = async () => null;
|
|
66
|
+
noopProvider.__name = "cosiall";
|
|
67
|
+
return noopProvider;
|
|
68
|
+
}
|
|
69
|
+
async function fetchEmail(candidate) {
|
|
70
|
+
// Extract lookup parameters in priority order
|
|
71
|
+
const objectUrn = extractObjectUrn(candidate);
|
|
72
|
+
const linkedInUrl = extractLinkedInUrl(candidate);
|
|
73
|
+
const vanity = extractVanity(candidate);
|
|
74
|
+
// Must have at least one lookup parameter
|
|
75
|
+
if (!objectUrn && !linkedInUrl && !vanity) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const result = await (0, cosiall_client_1.fetchProfileEmailsFromCosiall)({
|
|
80
|
+
objectUrn: objectUrn || undefined,
|
|
81
|
+
linkedInUrl: linkedInUrl || undefined,
|
|
82
|
+
vanity: vanity || undefined,
|
|
83
|
+
});
|
|
84
|
+
// No emails found
|
|
85
|
+
if (!result.emails || result.emails.length === 0) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
// Build multi-result with all emails
|
|
89
|
+
const emails = result.emails.map((email) => ({
|
|
90
|
+
email,
|
|
91
|
+
verified: true, // Cosiall data is from LinkedIn profiles
|
|
92
|
+
confidence: 85, // Good confidence - these are profile-associated emails
|
|
93
|
+
metadata: {
|
|
94
|
+
profileId: result.profileId,
|
|
95
|
+
objectUrn: result.objectUrn,
|
|
96
|
+
linkedInUrl: result.linkedInUrl,
|
|
97
|
+
source: "cosiall",
|
|
98
|
+
},
|
|
99
|
+
}));
|
|
100
|
+
return { emails };
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Silently fail - provider failures shouldn't stop the enrichment flow
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Mark provider name for orchestrator
|
|
108
|
+
fetchEmail.__name = "cosiall";
|
|
109
|
+
return fetchEmail;
|
|
110
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Email Enrichment Providers
|
|
3
3
|
*/
|
|
4
|
-
export { createConstructProvider } from
|
|
5
|
-
export { createLddProvider } from
|
|
6
|
-
export { createSmartProspectProvider } from
|
|
7
|
-
export {
|
|
8
|
-
export {
|
|
9
|
-
export {
|
|
10
|
-
export {
|
|
4
|
+
export { createConstructProvider } from "./construct";
|
|
5
|
+
export { createLddProvider } from "./ldd";
|
|
6
|
+
export { createSmartProspectProvider } from "./smartprospect";
|
|
7
|
+
export { createCosiallProvider } from "./cosiall";
|
|
8
|
+
export { createHunterProvider } from "./hunter";
|
|
9
|
+
export { createDropcontactProvider } from "./dropcontact";
|
|
10
|
+
export { createBouncerProvider, verifyEmailWithBouncer, checkCatchAllDomain, verifyEmailsBatch, } from "./bouncer";
|
|
11
|
+
export { createSnovioProvider, findEmailsWithSnovio, verifyEmailWithSnovio, clearSnovioTokenCache, } from "./snovio";
|
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
* Email Enrichment Providers
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.clearSnovioTokenCache = exports.verifyEmailWithSnovio = exports.findEmailsWithSnovio = exports.createSnovioProvider = exports.verifyEmailsBatch = exports.checkCatchAllDomain = exports.verifyEmailWithBouncer = exports.createBouncerProvider = exports.createDropcontactProvider = exports.createHunterProvider = exports.createSmartProspectProvider = exports.createLddProvider = exports.createConstructProvider = void 0;
|
|
6
|
+
exports.clearSnovioTokenCache = exports.verifyEmailWithSnovio = exports.findEmailsWithSnovio = exports.createSnovioProvider = exports.verifyEmailsBatch = exports.checkCatchAllDomain = exports.verifyEmailWithBouncer = exports.createBouncerProvider = exports.createDropcontactProvider = exports.createHunterProvider = exports.createCosiallProvider = exports.createSmartProspectProvider = exports.createLddProvider = exports.createConstructProvider = void 0;
|
|
7
7
|
var construct_1 = require("./construct");
|
|
8
8
|
Object.defineProperty(exports, "createConstructProvider", { enumerable: true, get: function () { return construct_1.createConstructProvider; } });
|
|
9
9
|
var ldd_1 = require("./ldd");
|
|
10
10
|
Object.defineProperty(exports, "createLddProvider", { enumerable: true, get: function () { return ldd_1.createLddProvider; } });
|
|
11
11
|
var smartprospect_1 = require("./smartprospect");
|
|
12
12
|
Object.defineProperty(exports, "createSmartProspectProvider", { enumerable: true, get: function () { return smartprospect_1.createSmartProspectProvider; } });
|
|
13
|
+
var cosiall_1 = require("./cosiall");
|
|
14
|
+
Object.defineProperty(exports, "createCosiallProvider", { enumerable: true, get: function () { return cosiall_1.createCosiallProvider; } });
|
|
13
15
|
var hunter_1 = require("./hunter");
|
|
14
16
|
Object.defineProperty(exports, "createHunterProvider", { enumerable: true, get: function () { return hunter_1.createHunterProvider; } });
|
|
15
17
|
var dropcontact_1 = require("./dropcontact");
|
|
@@ -45,7 +45,7 @@ export interface ProviderMultiResult {
|
|
|
45
45
|
/**
|
|
46
46
|
* Email type classification
|
|
47
47
|
*/
|
|
48
|
-
export type EmailType =
|
|
48
|
+
export type EmailType = "business" | "personal" | "disposable" | "role" | "unknown";
|
|
49
49
|
/**
|
|
50
50
|
* Individual enriched email with full metadata
|
|
51
51
|
*/
|
|
@@ -179,11 +179,11 @@ export interface BouncerConfig {
|
|
|
179
179
|
/**
|
|
180
180
|
* Bouncer verification result status
|
|
181
181
|
*/
|
|
182
|
-
export type BouncerStatus =
|
|
182
|
+
export type BouncerStatus = "deliverable" | "undeliverable" | "risky" | "unknown";
|
|
183
183
|
/**
|
|
184
184
|
* Bouncer verification result reason
|
|
185
185
|
*/
|
|
186
|
-
export type BouncerReason =
|
|
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
187
|
/**
|
|
188
188
|
* Bouncer API response for single email verification
|
|
189
189
|
*/
|
|
@@ -232,7 +232,7 @@ export interface SnovioConfig {
|
|
|
232
232
|
/**
|
|
233
233
|
* Snov.io email verification status
|
|
234
234
|
*/
|
|
235
|
-
export type SnovioVerificationStatus =
|
|
235
|
+
export type SnovioVerificationStatus = "valid" | "not_valid" | "catch_all" | "unverifiable" | "unknown";
|
|
236
236
|
/**
|
|
237
237
|
* Snov.io email result
|
|
238
238
|
*/
|
|
@@ -244,7 +244,7 @@ export interface SnovioEmailResult {
|
|
|
244
244
|
position?: string;
|
|
245
245
|
sourcePage?: string;
|
|
246
246
|
companyName?: string;
|
|
247
|
-
type?:
|
|
247
|
+
type?: "prospect" | "personal";
|
|
248
248
|
status?: string;
|
|
249
249
|
}
|
|
250
250
|
/**
|
|
@@ -274,6 +274,16 @@ export interface ConstructConfig {
|
|
|
274
274
|
/** Delay between SMTP verification checks in ms (default: 2000) */
|
|
275
275
|
smtpVerifyDelayMs?: number;
|
|
276
276
|
}
|
|
277
|
+
/**
|
|
278
|
+
* Cosiall Profile Emails provider configuration
|
|
279
|
+
*
|
|
280
|
+
* Uses Cosiall FlexIQ Profile Emails API to lookup emails for LinkedIn profiles.
|
|
281
|
+
* This is FREE - no API key needed (uses global Cosiall config).
|
|
282
|
+
*/
|
|
283
|
+
export interface CosiallConfig {
|
|
284
|
+
/** Whether to enable the provider (default: true) */
|
|
285
|
+
enabled?: boolean;
|
|
286
|
+
}
|
|
277
287
|
/**
|
|
278
288
|
* All provider configurations
|
|
279
289
|
*/
|
|
@@ -281,6 +291,8 @@ export interface ProvidersConfig {
|
|
|
281
291
|
construct?: ConstructConfig;
|
|
282
292
|
ldd?: LddConfig;
|
|
283
293
|
smartprospect?: SmartProspectConfig;
|
|
294
|
+
/** Cosiall Profile Emails (FREE - uses global Cosiall config) */
|
|
295
|
+
cosiall?: CosiallConfig;
|
|
284
296
|
hunter?: HunterConfig;
|
|
285
297
|
dropcontact?: DropcontactConfig;
|
|
286
298
|
/** Bouncer.io for SMTP email verification (99%+ accuracy) */
|
|
@@ -365,13 +377,14 @@ export interface EnrichmentClient {
|
|
|
365
377
|
/**
|
|
366
378
|
* Available provider names
|
|
367
379
|
*/
|
|
368
|
-
export type ProviderName = "construct" | "ldd" | "smartprospect" | "hunter" | "dropcontact" | "bouncer" | "snovio";
|
|
380
|
+
export type ProviderName = "construct" | "ldd" | "smartprospect" | "cosiall" | "hunter" | "dropcontact" | "bouncer" | "snovio";
|
|
369
381
|
/**
|
|
370
382
|
* Default provider order - 2-Phase Strategy
|
|
371
383
|
*
|
|
372
384
|
* PHASE 1 - Free lookups (run in parallel):
|
|
373
385
|
* - ldd: LinkedIn Data Dump - real verified emails (FREE with subscription)
|
|
374
386
|
* - smartprospect: SmartLead API - real verified emails (FREE with subscription)
|
|
387
|
+
* - cosiall: Cosiall Profile Emails - emails from LinkedIn profiles (FREE)
|
|
375
388
|
* - construct: Pattern guessing + MX check (FREE)
|
|
376
389
|
*
|
|
377
390
|
* PHASE 2 - Paid verification/finding (only if Phase 1 inconclusive):
|
|
@@ -388,6 +401,7 @@ export declare const DEFAULT_PROVIDER_ORDER: ProviderName[];
|
|
|
388
401
|
* Costs based on 2025 pricing:
|
|
389
402
|
* - ldd: FREE (subscription-based)
|
|
390
403
|
* - smartprospect: FREE (included in SmartLead subscription)
|
|
404
|
+
* - cosiall: FREE (uses global Cosiall config)
|
|
391
405
|
* - construct: FREE (pattern guessing + MX check)
|
|
392
406
|
* - bouncer: $0.006/email (SMTP verification, 99%+ accuracy)
|
|
393
407
|
* - snovio: $0.02/email (email finding + verification)
|
|
@@ -492,23 +506,23 @@ export interface SmartProspectSearchFilters {
|
|
|
492
506
|
/**
|
|
493
507
|
* SmartProspect Department values (exact API values)
|
|
494
508
|
*/
|
|
495
|
-
export type SmartProspectDepartment =
|
|
509
|
+
export type SmartProspectDepartment = "Engineering" | "Finance & Administration" | "Human Resources" | "IT & IS" | "Marketing" | "Operations" | "Other" | "Support" | "Sales";
|
|
496
510
|
/**
|
|
497
511
|
* SmartProspect Level/Seniority values (exact API values)
|
|
498
512
|
*/
|
|
499
|
-
export type SmartProspectLevel =
|
|
513
|
+
export type SmartProspectLevel = "Staff" | "Manager-Level" | "Director-Level" | "VP-Level" | "C-Level";
|
|
500
514
|
/**
|
|
501
515
|
* SmartProspect Headcount ranges (exact API values)
|
|
502
516
|
*/
|
|
503
|
-
export type SmartProspectHeadcount =
|
|
517
|
+
export type SmartProspectHeadcount = "0 - 25" | "25 - 100" | "100 - 250" | "250 - 1000" | "1K - 10K" | "10K - 50K" | "50K - 100K" | "> 100K";
|
|
504
518
|
/**
|
|
505
519
|
* SmartProspect Revenue ranges (exact API values)
|
|
506
520
|
*/
|
|
507
|
-
export type SmartProspectRevenue =
|
|
521
|
+
export type SmartProspectRevenue = "$0 - 1M" | "$1 - 10M" | "$10 - 50M" | "$50 - 100M" | "$100 - 250M" | "$250 - 500M" | "$500M - 1B" | "> $1B";
|
|
508
522
|
/**
|
|
509
523
|
* SmartProspect Industry values (exact API values)
|
|
510
524
|
*/
|
|
511
|
-
export type SmartProspectIndustry =
|
|
525
|
+
export type SmartProspectIndustry = "Software & Internet" | "Business Services" | "Real Estate & Construction" | "Financial Services" | "Healthcare, Pharmaceuticals, & Biotech" | "Retail" | "Consumer Services" | "Education" | "Media & Entertainment" | "Travel, Recreation, and Leisure" | "Transportation & Storage" | "Manufacturing" | "Wholesale & Distribution" | "Non-Profit" | "Energy & Utilities" | "Government" | "Agriculture & Mining" | "Computers & Electronics" | "Telecommunications" | "Other";
|
|
512
526
|
/**
|
|
513
527
|
* SmartProspect Sub-Industry values (exact API values - partial list)
|
|
514
528
|
* Note: This is a subset of available sub-industries. The API accepts many more.
|
package/dist/enrichment/types.js
CHANGED
|
@@ -13,6 +13,7 @@ exports.SMARTPROSPECT_SUB_INDUSTRIES = exports.PROVIDER_COSTS = exports.DEFAULT_
|
|
|
13
13
|
* PHASE 1 - Free lookups (run in parallel):
|
|
14
14
|
* - ldd: LinkedIn Data Dump - real verified emails (FREE with subscription)
|
|
15
15
|
* - smartprospect: SmartLead API - real verified emails (FREE with subscription)
|
|
16
|
+
* - cosiall: Cosiall Profile Emails - emails from LinkedIn profiles (FREE)
|
|
16
17
|
* - construct: Pattern guessing + MX check (FREE)
|
|
17
18
|
*
|
|
18
19
|
* PHASE 2 - Paid verification/finding (only if Phase 1 inconclusive):
|
|
@@ -25,6 +26,7 @@ exports.SMARTPROSPECT_SUB_INDUSTRIES = exports.PROVIDER_COSTS = exports.DEFAULT_
|
|
|
25
26
|
exports.DEFAULT_PROVIDER_ORDER = [
|
|
26
27
|
"ldd",
|
|
27
28
|
"smartprospect",
|
|
29
|
+
"cosiall",
|
|
28
30
|
"construct",
|
|
29
31
|
"bouncer",
|
|
30
32
|
"snovio",
|
|
@@ -36,6 +38,7 @@ exports.DEFAULT_PROVIDER_ORDER = [
|
|
|
36
38
|
* Costs based on 2025 pricing:
|
|
37
39
|
* - ldd: FREE (subscription-based)
|
|
38
40
|
* - smartprospect: FREE (included in SmartLead subscription)
|
|
41
|
+
* - cosiall: FREE (uses global Cosiall config)
|
|
39
42
|
* - construct: FREE (pattern guessing + MX check)
|
|
40
43
|
* - bouncer: $0.006/email (SMTP verification, 99%+ accuracy)
|
|
41
44
|
* - snovio: $0.02/email (email finding + verification)
|
|
@@ -46,6 +49,7 @@ exports.PROVIDER_COSTS = {
|
|
|
46
49
|
construct: 0,
|
|
47
50
|
ldd: 0,
|
|
48
51
|
smartprospect: 0,
|
|
52
|
+
cosiall: 0,
|
|
49
53
|
hunter: 0.005,
|
|
50
54
|
dropcontact: 0.01,
|
|
51
55
|
bouncer: 0.006,
|
|
@@ -57,46 +61,46 @@ exports.PROVIDER_COSTS = {
|
|
|
57
61
|
*/
|
|
58
62
|
exports.SMARTPROSPECT_SUB_INDUSTRIES = [
|
|
59
63
|
// Software & Internet
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
"Internet",
|
|
65
|
+
"Information Technology and Services",
|
|
66
|
+
"Information Services",
|
|
67
|
+
"Computer Software",
|
|
68
|
+
"Computer & Network Security",
|
|
69
|
+
"Computer Games",
|
|
66
70
|
// Real Estate & Construction
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
"Glass, Ceramics & Concrete",
|
|
72
|
+
"Construction",
|
|
73
|
+
"Commercial Real Estate",
|
|
74
|
+
"Civil Engineering",
|
|
75
|
+
"Building Materials",
|
|
76
|
+
"Architecture & Planning",
|
|
73
77
|
// Business Services
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
78
|
+
"Writing and Editing",
|
|
79
|
+
"Translation and Localization",
|
|
80
|
+
"Think Tanks",
|
|
81
|
+
"Staffing and Recruiting",
|
|
82
|
+
"Security and Investigations",
|
|
83
|
+
"Public Safety",
|
|
84
|
+
"Public Relations and Communications",
|
|
85
|
+
"Program Development",
|
|
86
|
+
"Professional Training & Coaching",
|
|
87
|
+
"Market Research",
|
|
88
|
+
"Marketing and Advertising",
|
|
89
|
+
"Management Consulting",
|
|
90
|
+
"Legal Services",
|
|
91
|
+
"Law Practice",
|
|
92
|
+
"Law Enforcement",
|
|
93
|
+
"International Trade and Development",
|
|
94
|
+
"Import and Export",
|
|
95
|
+
"Human Resources",
|
|
96
|
+
"Graphic Design",
|
|
97
|
+
"Facilities Services",
|
|
98
|
+
"Executive Office",
|
|
99
|
+
"Events Services",
|
|
100
|
+
"Environmental Services",
|
|
101
|
+
"Design",
|
|
102
|
+
"Business Supplies and Equipment",
|
|
103
|
+
"Animation",
|
|
104
|
+
"Alternative Dispute Resolution",
|
|
105
|
+
"Outsourcing/Offshoring",
|
|
102
106
|
];
|