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.
@@ -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";
@@ -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.matchContacts = 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.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 = 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 './types';
8
- import type { SalesLeadSearchResult } from '../types';
9
- import { type SmartProspectClient } from './providers/smartprospect';
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: 'exact' | 'high' | 'medium' | 'low' | 'none';
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[]): 'exact' | 'high' | 'medium' | 'low' | 'none';
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: 'exact' | 'high' | 'medium' | 'low' | 'none';
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 = 'ldd' | 'smartprospect' | 'linkedin' | 'pattern' | 'hunter' | 'bouncer' | 'snovio';
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: 'business' | 'personal' | 'unknown';
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 'name' in input && !('firstName' in input);
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: '', state: '', country: '' };
148
- const parts = geoRegion.split(',').map((p) => p.trim());
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('firstName:exact');
248
+ matchedFields.push("firstName:exact");
249
249
  }
250
- else if (fuzzyNames && fuzzyMatch(linkedin.firstName, smartprospect.firstName)) {
250
+ else if (fuzzyNames &&
251
+ fuzzyMatch(linkedin.firstName, smartprospect.firstName)) {
251
252
  score += 15;
252
- matchedFields.push('firstName:fuzzy');
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('lastName:exact');
258
+ matchedFields.push("lastName:exact");
258
259
  }
259
- else if (fuzzyNames && fuzzyMatch(linkedin.lastName, smartprospect.lastName)) {
260
+ else if (fuzzyNames &&
261
+ fuzzyMatch(linkedin.lastName, smartprospect.lastName)) {
260
262
  score += 15;
261
- matchedFields.push('lastName:fuzzy');
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('company:exact');
270
+ matchedFields.push("company:exact");
269
271
  }
270
272
  else if (fuzzyCompany && fuzzyMatch(liCompany, spCompany, 0.8)) {
271
273
  score += 25;
272
- matchedFields.push('company:fuzzy');
274
+ matchedFields.push("company:fuzzy");
273
275
  }
274
276
  else if (containsMatch(liCompany, spCompany)) {
275
277
  score += 15;
276
- matchedFields.push('company:contains');
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('title:exact');
285
+ matchedFields.push("title:exact");
284
286
  }
285
287
  else if (fuzzyMatch(liTitle, spTitle, 0.75)) {
286
288
  score += 12;
287
- matchedFields.push('title:fuzzy');
289
+ matchedFields.push("title:fuzzy");
288
290
  }
289
291
  else if (containsMatch(liTitle, spTitle)) {
290
292
  score += 8;
291
- matchedFields.push('title:contains');
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('country:exact');
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('state:exact');
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('city:exact');
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('firstName:exact')) &&
320
- matchedFields.some((f) => f.startsWith('lastName:exact'));
321
- const hasCompanyMatch = matchedFields.some((f) => f.startsWith('company:'));
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 'exact';
325
+ return "exact";
324
326
  }
325
327
  else if (confidence >= 70 && hasNameMatch) {
326
- return 'high';
328
+ return "high";
327
329
  }
328
330
  else if (confidence >= 50) {
329
- return 'medium';
331
+ return "medium";
330
332
  }
331
333
  else if (confidence >= 30) {
332
- return 'low';
334
+ return "low";
333
335
  }
334
- return 'none';
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: 'none',
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 = 'Failed to create SmartProspect client - check credentials';
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 = 'No candidates found in SmartProspect';
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, { minConfidence, fuzzyNames, fuzzyCompany });
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([matchResult.smartProspectContact.id]);
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 : 'Unknown error';
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: 'none',
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 && includeCompany && filters.companyName) {
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 = 'No candidates found in SmartProspect';
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([matchResult.smartProspectContact.id]);
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 = enrichedContact.emailDeliverability || 0;
671
- result.verificationStatus = enrichedContact.verificationStatus || null;
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 : 'Unknown error';
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('ldd');
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('smartprospect');
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('linkedin');
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 : 'Company lookup failed'}`);
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 && companyDomain && bestConfidenceAfterFreeProviders < paidProviderThreshold) {
858
- result.providersQueried.push('pattern');
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 || config.bouncer?.apiKey || config.snovio?.userId) {
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
- linkedin: 2, // LinkedIn company lookup (for domain discovery, doesn't provide emails)
884
- pattern: 3,
885
- hunter: 4,
886
- bouncer: 5,
887
- snovio: 6,
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('http') ? url : `https://${url}`;
924
- return new URL(fullUrl).hostname.replace(/^www\./, '').toLowerCase();
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.replace(/^(https?:\/\/)?(www\.)?/, '').split('/')[0].toLowerCase() || null;
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 && 'emails' in lddResult && lddResult.emails.length > 0) {
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: 'ldd',
983
+ source: "ldd",
958
984
  confidence: emailData.confidence ?? 90,
959
- type: emailData.metadata?.emailTypeClassified || 'unknown',
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 : 'Unknown error'}`);
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('SmartProspect: Failed to create client');
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('/')[0]
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([matchResult.smartProspectContact.id]);
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: 'smartprospect',
1111
+ source: "smartprospect",
1036
1112
  confidence: matchResult.confidence,
1037
- type: 'business',
1038
- verified: enrichedContact.verificationStatus === 'verified',
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 : 'Unknown error'}`);
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('./providers/construct')));
1065
- const constructProvider = createConstructProvider({ maxAttempts: 12, timeoutMs: 3000 });
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 && 'emails' in constructResult && constructResult.emails.length > 0) {
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: 'pattern',
1156
+ source: "pattern",
1076
1157
  confidence: emailData.confidence ?? 50,
1077
- type: 'business',
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 : 'Unknown error'}`);
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 './construct';
5
- export { createLddProvider } from './ldd';
6
- export { createSmartProspectProvider } from './smartprospect';
7
- export { createHunterProvider } from './hunter';
8
- export { createDropcontactProvider } from './dropcontact';
9
- export { createBouncerProvider, verifyEmailWithBouncer, checkCatchAllDomain, verifyEmailsBatch } from './bouncer';
10
- export { createSnovioProvider, findEmailsWithSnovio, verifyEmailWithSnovio, clearSnovioTokenCache } from './snovio';
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 = 'business' | 'personal' | 'disposable' | 'role' | 'unknown';
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 = 'deliverable' | 'undeliverable' | 'risky' | 'unknown';
182
+ export type BouncerStatus = "deliverable" | "undeliverable" | "risky" | "unknown";
183
183
  /**
184
184
  * Bouncer verification result reason
185
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';
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 = 'valid' | 'not_valid' | 'catch_all' | 'unverifiable' | 'unknown';
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?: 'prospect' | 'personal';
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 = 'Engineering' | 'Finance & Administration' | 'Human Resources' | 'IT & IS' | 'Marketing' | 'Operations' | 'Other' | 'Support' | 'Sales';
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 = 'Staff' | 'Manager-Level' | 'Director-Level' | 'VP-Level' | 'C-Level';
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 = '0 - 25' | '25 - 100' | '100 - 250' | '250 - 1000' | '1K - 10K' | '10K - 50K' | '50K - 100K' | '> 100K';
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 = '$0 - 1M' | '$1 - 10M' | '$10 - 50M' | '$50 - 100M' | '$100 - 250M' | '$250 - 500M' | '$500M - 1B' | '> $1B';
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 = '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';
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.
@@ -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
- 'Internet',
61
- 'Information Technology and Services',
62
- 'Information Services',
63
- 'Computer Software',
64
- 'Computer & Network Security',
65
- 'Computer Games',
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
- 'Glass, Ceramics & Concrete',
68
- 'Construction',
69
- 'Commercial Real Estate',
70
- 'Civil Engineering',
71
- 'Building Materials',
72
- 'Architecture & Planning',
71
+ "Glass, Ceramics & Concrete",
72
+ "Construction",
73
+ "Commercial Real Estate",
74
+ "Civil Engineering",
75
+ "Building Materials",
76
+ "Architecture & Planning",
73
77
  // Business Services
74
- 'Writing and Editing',
75
- 'Translation and Localization',
76
- 'Think Tanks',
77
- 'Staffing and Recruiting',
78
- 'Security and Investigations',
79
- 'Public Safety',
80
- 'Public Relations and Communications',
81
- 'Program Development',
82
- 'Professional Training & Coaching',
83
- 'Market Research',
84
- 'Marketing and Advertising',
85
- 'Management Consulting',
86
- 'Legal Services',
87
- 'Law Practice',
88
- 'Law Enforcement',
89
- 'International Trade and Development',
90
- 'Import and Export',
91
- 'Human Resources',
92
- 'Graphic Design',
93
- 'Facilities Services',
94
- 'Executive Office',
95
- 'Events Services',
96
- 'Environmental Services',
97
- 'Design',
98
- 'Business Supplies and Equipment',
99
- 'Animation',
100
- 'Alternative Dispute Resolution',
101
- 'Outsourcing/Offshoring',
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
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linkedin-secret-sauce",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "Private LinkedIn Sales Navigator client with automatic cookie management",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",