linkedin-secret-sauce 0.12.3 → 0.12.5

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.
Files changed (163) hide show
  1. package/README.md +6 -49
  2. package/dist/enrichment/index.d.ts +3 -5
  3. package/dist/enrichment/index.js +4 -20
  4. package/dist/enrichment/matching.d.ts +4 -8
  5. package/dist/enrichment/matching.js +165 -84
  6. package/dist/enrichment/orchestrator.d.ts +1 -1
  7. package/dist/enrichment/orchestrator.js +51 -44
  8. package/dist/enrichment/providers/bounceban.js +19 -10
  9. package/dist/enrichment/providers/construct.js +97 -89
  10. package/dist/enrichment/providers/cosiall.js +2 -2
  11. package/dist/enrichment/providers/hunter.js +2 -1
  12. package/dist/enrichment/providers/index.d.ts +0 -2
  13. package/dist/enrichment/providers/index.js +1 -8
  14. package/dist/enrichment/providers/ldd.js +4 -2
  15. package/dist/enrichment/providers/smartprospect.d.ts +5 -2
  16. package/dist/enrichment/providers/smartprospect.js +64 -27
  17. package/dist/enrichment/providers/snovio.d.ts +4 -4
  18. package/dist/enrichment/providers/snovio.js +21 -17
  19. package/dist/enrichment/providers/trykitt.d.ts +2 -2
  20. package/dist/enrichment/providers/trykitt.js +86 -21
  21. package/dist/enrichment/types.d.ts +8 -71
  22. package/dist/enrichment/types.js +3 -8
  23. package/dist/enrichment/utils/rate-limiter.js +0 -2
  24. package/dist/index.d.ts +2 -4
  25. package/dist/index.js +2 -2
  26. package/docs/ENRICHMENT.md +3 -45
  27. package/docs/INTEGRATION.md +1 -3
  28. package/docs/PLAYGROUND.md +4 -9
  29. package/docs/api/assets/hierarchy.js +1 -1
  30. package/docs/api/assets/navigation.js +1 -1
  31. package/docs/api/assets/search.js +1 -1
  32. package/docs/api/classes/LinkedInClientError.html +4 -4
  33. package/docs/api/functions/_testGetAccountCookies.html +2 -2
  34. package/docs/api/functions/_testGetAccountEntry.html +2 -2
  35. package/docs/api/functions/_testGetAllAccountIds.html +2 -2
  36. package/docs/api/functions/_testGetPoolState.html +2 -2
  37. package/docs/api/functions/adminResetAccount.html +1 -1
  38. package/docs/api/functions/adminSetCooldown.html +1 -1
  39. package/docs/api/functions/buildCookieHeader.html +1 -1
  40. package/docs/api/functions/clearAllSmartLeadTokens.html +2 -2
  41. package/docs/api/functions/clearRequestHistory.html +1 -1
  42. package/docs/api/functions/clearSessionAccount.html +1 -1
  43. package/docs/api/functions/clearSmartLeadToken.html +2 -2
  44. package/docs/api/functions/createEnrichmentClient.html +3 -3
  45. package/docs/api/functions/extractCsrfToken.html +1 -1
  46. package/docs/api/functions/extractLinkedInHandle.html +2 -2
  47. package/docs/api/functions/fetchCookiesFromCosiall.html +2 -2
  48. package/docs/api/functions/fetchProfileEmailsFromCosiall.html +2 -2
  49. package/docs/api/functions/forceRefreshCookies.html +1 -1
  50. package/docs/api/functions/getAccountForSession.html +1 -1
  51. package/docs/api/functions/getAccountsSummary.html +1 -1
  52. package/docs/api/functions/getCompaniesBatch.html +2 -2
  53. package/docs/api/functions/getCompanyById.html +2 -2
  54. package/docs/api/functions/getCompanyByUrl.html +1 -1
  55. package/docs/api/functions/getConfig.html +1 -1
  56. package/docs/api/functions/getCookiePoolHealth.html +1 -1
  57. package/docs/api/functions/getProfileByUrn.html +2 -2
  58. package/docs/api/functions/getProfileByVanity.html +2 -2
  59. package/docs/api/functions/getProfilesBatch.html +1 -1
  60. package/docs/api/functions/getRequestHistory.html +1 -1
  61. package/docs/api/functions/getSalesNavigatorProfileDetails.html +1 -1
  62. package/docs/api/functions/getSalesNavigatorProfileFull.html +2 -2
  63. package/docs/api/functions/getSmartLeadToken.html +1 -1
  64. package/docs/api/functions/getSmartLeadTokenCacheStats.html +2 -2
  65. package/docs/api/functions/getSmartLeadUser.html +2 -2
  66. package/docs/api/functions/getSnapshot.html +1 -1
  67. package/docs/api/functions/getYearsAtCompanyOptions.html +2 -2
  68. package/docs/api/functions/getYearsInPositionOptions.html +2 -2
  69. package/docs/api/functions/getYearsOfExperienceOptions.html +2 -2
  70. package/docs/api/functions/incrementMetric.html +1 -1
  71. package/docs/api/functions/initializeCookiePool.html +1 -1
  72. package/docs/api/functions/initializeLinkedInClient.html +1 -1
  73. package/docs/api/functions/isBusinessEmail.html +2 -2
  74. package/docs/api/functions/isDisposableDomain.html +2 -2
  75. package/docs/api/functions/isDisposableEmail.html +2 -2
  76. package/docs/api/functions/isPersonalDomain.html +2 -2
  77. package/docs/api/functions/isPersonalEmail.html +2 -2
  78. package/docs/api/functions/isRoleAccount.html +2 -2
  79. package/docs/api/functions/isValidEmailSyntax.html +2 -2
  80. package/docs/api/functions/parseFullProfile.html +2 -2
  81. package/docs/api/functions/parseSalesSearchResults.html +1 -1
  82. package/docs/api/functions/reportAccountFailure.html +1 -1
  83. package/docs/api/functions/reportAccountSuccess.html +1 -1
  84. package/docs/api/functions/resolveCompanyUniversalName.html +1 -1
  85. package/docs/api/functions/searchSalesLeads.html +2 -2
  86. package/docs/api/functions/selectAccountForRequest.html +1 -1
  87. package/docs/api/functions/setAccountForSession.html +1 -1
  88. package/docs/api/functions/typeahead.html +1 -1
  89. package/docs/api/functions/verifyEmailMx.html +1 -1
  90. package/docs/api/hierarchy.html +1 -1
  91. package/docs/api/index.html +3 -3
  92. package/docs/api/interfaces/AccountCookies.html +2 -2
  93. package/docs/api/interfaces/BatchEnrichmentOptions.html +8 -8
  94. package/docs/api/interfaces/CacheAdapter.html +4 -4
  95. package/docs/api/interfaces/CanonicalEmail.html +8 -8
  96. package/docs/api/interfaces/Company.html +2 -2
  97. package/docs/api/interfaces/ConstructConfig.html +5 -5
  98. package/docs/api/interfaces/CosiallProfileEmailsResponse.html +6 -6
  99. package/docs/api/interfaces/EnrichmentCandidate.html +4 -4
  100. package/docs/api/interfaces/EnrichmentClient.html +6 -6
  101. package/docs/api/interfaces/EnrichmentClientConfig.html +7 -7
  102. package/docs/api/interfaces/EnrichmentLogger.html +3 -3
  103. package/docs/api/interfaces/EnrichmentOptions.html +6 -6
  104. package/docs/api/interfaces/HunterConfig.html +3 -3
  105. package/docs/api/interfaces/LddConfig.html +3 -3
  106. package/docs/api/interfaces/LddProfileData.html +2 -2
  107. package/docs/api/interfaces/LinkedInClientConfig.html +2 -2
  108. package/docs/api/interfaces/LinkedInCookie.html +2 -2
  109. package/docs/api/interfaces/LinkedInPosition.html +2 -2
  110. package/docs/api/interfaces/LinkedInProfile.html +2 -2
  111. package/docs/api/interfaces/LinkedInSpotlightBadge.html +2 -2
  112. package/docs/api/interfaces/LinkedInTenure.html +2 -2
  113. package/docs/api/interfaces/Metrics.html +2 -2
  114. package/docs/api/interfaces/MetricsSnapshot.html +2 -2
  115. package/docs/api/interfaces/ProfileEducation.html +2 -2
  116. package/docs/api/interfaces/ProfileEmailsLookupOptions.html +5 -5
  117. package/docs/api/interfaces/ProfilePosition.html +2 -2
  118. package/docs/api/interfaces/ProfileSkill.html +2 -2
  119. package/docs/api/interfaces/ProviderResult.html +6 -6
  120. package/docs/api/interfaces/ProvidersConfig.html +6 -9
  121. package/docs/api/interfaces/RequestHistoryEntry.html +2 -2
  122. package/docs/api/interfaces/SalesLeadSearchResult.html +2 -2
  123. package/docs/api/interfaces/SalesNavigatorContactInfo.html +2 -2
  124. package/docs/api/interfaces/SalesNavigatorPosition.html +2 -2
  125. package/docs/api/interfaces/SalesNavigatorProfile.html +2 -2
  126. package/docs/api/interfaces/SalesNavigatorProfileFull.html +4 -4
  127. package/docs/api/interfaces/SearchSalesResult.html +2 -2
  128. package/docs/api/interfaces/SmartLeadAuthConfig.html +4 -4
  129. package/docs/api/interfaces/SmartLeadCredentials.html +2 -2
  130. package/docs/api/interfaces/SmartLeadLoginResponse.html +2 -2
  131. package/docs/api/interfaces/SmartLeadUser.html +2 -2
  132. package/docs/api/interfaces/SmartProspectConfig.html +8 -8
  133. package/docs/api/interfaces/SmartProspectContact.html +2 -2
  134. package/docs/api/interfaces/SmartProspectSearchFilters.html +21 -21
  135. package/docs/api/interfaces/TypeaheadItem.html +2 -2
  136. package/docs/api/interfaces/TypeaheadResult.html +2 -2
  137. package/docs/api/interfaces/VerificationResult.html +9 -9
  138. package/docs/api/types/CostCallback.html +2 -2
  139. package/docs/api/types/Geo.html +2 -2
  140. package/docs/api/types/LddApiResponse.html +1 -1
  141. package/docs/api/types/ProviderFunc.html +2 -2
  142. package/docs/api/types/ProviderName.html +2 -2
  143. package/docs/api/types/SalesSearchFilters.html +2 -2
  144. package/docs/api/types/TypeaheadType.html +1 -1
  145. package/docs/api/variables/COMPANY_SIZE_OPTIONS.html +1 -1
  146. package/docs/api/variables/DEFAULT_PROVIDER_ORDER.html +3 -4
  147. package/docs/api/variables/DISPOSABLE_DOMAINS.html +2 -2
  148. package/docs/api/variables/FUNCTION_OPTIONS.html +1 -1
  149. package/docs/api/variables/INDUSTRY_OPTIONS.html +1 -1
  150. package/docs/api/variables/LANGUAGE_OPTIONS.html +1 -1
  151. package/docs/api/variables/PERSONAL_DOMAINS.html +2 -2
  152. package/docs/api/variables/PROVIDER_COSTS.html +3 -5
  153. package/docs/api/variables/REGION_OPTIONS.html +1 -1
  154. package/docs/api/variables/SENIORITY_OPTIONS.html +2 -2
  155. package/docs/api/variables/YEARS_OPTIONS.html +1 -1
  156. package/package.json +1 -1
  157. package/dist/enrichment/providers/apollo.d.ts +0 -11
  158. package/dist/enrichment/providers/apollo.js +0 -181
  159. package/dist/enrichment/providers/bouncer.d.ts +0 -67
  160. package/dist/enrichment/providers/bouncer.js +0 -231
  161. package/dist/enrichment/providers/dropcontact.d.ts +0 -22
  162. package/dist/enrichment/providers/dropcontact.js +0 -206
  163. package/docs/api/interfaces/DropcontactConfig.html +0 -3
package/README.md CHANGED
@@ -4,7 +4,7 @@ A complete LinkedIn Sales Navigator client + Email Enrichment library for Node.j
4
4
 
5
5
  **Two main modules:**
6
6
  1. **LinkedIn API** - Sales Navigator search, profiles, companies with automatic cookie/account rotation
7
- 2. **Email Enrichment** - Find business emails via multiple providers (11 providers including Hunter, Bouncer, TryKitt.ai, BounceBan, Snov.io, etc.)
7
+ 2. **Email Enrichment** - Find business emails via multiple providers (Hunter, TryKitt.ai, BounceBan, Snov.io, etc.)
8
8
 
9
9
  ## Documentation
10
10
 
@@ -250,9 +250,7 @@ Find and verify business emails using multiple providers with waterfall/parallel
250
250
  | **TryKitt.ai** | AI Finder | FREE** | AI-powered email finding with catch-all verification |
251
251
  | **Construct** | Pattern + MX | FREE | Pattern guessing with MX verification |
252
252
  | **BounceBan** | Verification | FREE/$0.003 | Catch-all specialist (85-95% accuracy) |
253
- | **Bouncer** | SMTP Verify | $0.006/email | Catch-all detection, 99%+ accuracy |
254
- | **Hunter** | Email Finder | $0.005/email | Domain search, email finder |
255
- | **Dropcontact** | Email Finder | $0.01/email | Email finding API |
253
+ | **Hunter** | Email Finder | $0.015/email | Domain search, email finder |
256
254
  | **Snov.io** | Email Finder | $0.02/email | Finding emails by name+domain |
257
255
 
258
256
  *SmartProspect is FREE if you have a SmartLead subscription
@@ -274,7 +272,7 @@ PHASE 2 - Pattern + Verification (if Phase 1 < 80% confidence):
274
272
  └── BounceBan → Catch-all verification (FREE single)
275
273
 
276
274
  PHASE 3 - Paid Finders (only if Phase 2 inconclusive):
277
- ├── Hunter → $0.005/email
275
+ ├── Hunter → $0.015/email
278
276
  └── Snov.io → $0.02/email
279
277
  ```
280
278
 
@@ -335,12 +333,11 @@ const enrichment = createEnrichmentClient({
335
333
  },
336
334
 
337
335
  // PAID providers - only used if FREE providers don't find email
338
- bouncer: { apiKey: process.env.BOUNCER_API_KEY },
336
+ hunter: { apiKey: process.env.HUNTER_API_KEY },
339
337
  snovio: {
340
338
  clientId: process.env.SNOVIO_CLIENT_ID,
341
339
  clientSecret: process.env.SNOVIO_CLIENT_SECRET
342
340
  },
343
- hunter: { apiKey: process.env.HUNTER_API_KEY },
344
341
  },
345
342
 
346
343
  // Optional: cost tracking callback
@@ -495,40 +492,6 @@ batchResults.forEach((result, email) => {
495
492
  });
496
493
  ```
497
494
 
498
- ### Email Verification with Bouncer
499
-
500
- ```typescript
501
- import {
502
- verifyEmailWithBouncer,
503
- checkCatchAllDomain,
504
- verifyEmailsBatch
505
- } from 'linkedin-secret-sauce';
506
-
507
- // Verify a single email
508
- const verification = await verifyEmailWithBouncer(
509
- 'john@example.com',
510
- { apiKey: process.env.BOUNCER_API_KEY }
511
- );
512
-
513
- console.log(verification.status); // 'deliverable' | 'undeliverable' | 'risky'
514
- console.log(verification.reason); // 'accepted_email' | 'rejected_email' | etc
515
- console.log(verification.acceptAll); // true = catch-all domain (can't verify)
516
- console.log(verification.disposable); // Is this a temp email?
517
- console.log(verification.role); // Is this info@, support@, etc?
518
-
519
- // Check if domain is catch-all (accepts any email)
520
- const isCatchAll = await checkCatchAllDomain('example.com', config);
521
- if (isCatchAll) {
522
- console.log('Cannot verify emails - domain accepts everything');
523
- }
524
-
525
- // Batch verify (with concurrency control)
526
- const results = await verifyEmailsBatch(
527
- ['john@example.com', 'jane@example.com'],
528
- { apiKey: process.env.BOUNCER_API_KEY }
529
- );
530
- ```
531
-
532
495
  ### Email Finding with Snov.io
533
496
 
534
497
  ```typescript
@@ -598,8 +561,7 @@ const enrichment = createEnrichmentClient({
598
561
  'trykitt', // FREE - AI finder
599
562
  'construct', // FREE - pattern matching
600
563
  'bounceban', // FREE single / $0.003 bulk
601
- 'hunter', // $0.005
602
- 'bouncer', // $0.006 (verification only)
564
+ 'hunter', // $0.015
603
565
  'snovio', // $0.02
604
566
  ],
605
567
 
@@ -671,8 +633,6 @@ const result = await getEmailsForLinkedInContact(
671
633
  apiKey: process.env.BOUNCEBAN_API_KEY,
672
634
  useDeepVerify: true,
673
635
  },
674
- // Bouncer for final verification
675
- bouncer: { apiKey: process.env.BOUNCER_API_KEY },
676
636
  }
677
637
  );
678
638
 
@@ -716,7 +676,6 @@ const enrichment = createEnrichmentClient({
716
676
  trykitt: { apiKey: process.env.TRYKITT_API_KEY },
717
677
  bounceban: { apiKey: process.env.BOUNCEBAN_API_KEY, useDeepVerify: true },
718
678
  hunter: { apiKey: process.env.HUNTER_API_KEY },
719
- bouncer: { apiKey: process.env.BOUNCER_API_KEY },
720
679
  },
721
680
  });
722
681
 
@@ -786,9 +745,7 @@ TRYKITT_API_KEY=... # FREE for individuals
786
745
  BOUNCEBAN_API_KEY=... # FREE single, $0.003 bulk
787
746
 
788
747
  # Email Enrichment - PAID providers
789
- HUNTER_API_KEY=... # $0.005/email
790
- BOUNCER_API_KEY=... # $0.006/email
791
- DROPCONTACT_API_KEY=... # $0.01/email
748
+ HUNTER_API_KEY=... # $0.015/email
792
749
  SNOVIO_CLIENT_ID=... # $0.02/email
793
750
  SNOVIO_CLIENT_SECRET=...
794
751
  ```
@@ -12,7 +12,7 @@
12
12
  * providers: {
13
13
  * ldd: { apiUrl: process.env.LDD_API_URL, apiToken: process.env.LDD_API_TOKEN },
14
14
  * smartprospect: { email: process.env.SMARTLEAD_EMAIL, password: process.env.SMARTLEAD_PASSWORD },
15
- * bouncer: { apiKey: process.env.BOUNCER_API_KEY },
15
+ * bounceban: { apiKey: process.env.BOUNCEBAN_API_KEY },
16
16
  * },
17
17
  * options: {
18
18
  * maxCostPerEmail: 0.05,
@@ -44,10 +44,8 @@ import { type EnrichmentClientConfig, type EnrichmentClient } from "./types";
44
44
  * - bounceban: BounceBan catch-all verification (FREE single / $0.003 bulk)
45
45
  *
46
46
  * PHASE 3 - Paid finders (only if Phase 2 inconclusive):
47
- * - hunter: Hunter.io ($0.005/email)
47
+ * - hunter: Hunter.io ($0.015/email)
48
48
  * - snovio: Snov.io ($0.02/email)
49
- *
50
- * Note: bouncer ($0.006) and dropcontact ($0.01) available but not in default order
51
49
  */
52
50
  /**
53
51
  * Create an enrichment client with the given configuration
@@ -62,7 +60,7 @@ export { isPersonalEmail, isBusinessEmail, isPersonalDomain, PERSONAL_DOMAINS, }
62
60
  export { isDisposableEmail, isDisposableDomain, DISPOSABLE_DOMAINS, } from "./utils/disposable-domains";
63
61
  export { isValidEmailSyntax, isRoleAccount, asciiFold, cleanNamePart, hostnameFromUrl, extractLinkedInUsername, } from "./utils/validation";
64
62
  export { verifyEmailMx, checkDomainCatchAll, verifyEmailsExist, } from "./verification/mx";
65
- export { createConstructProvider, createLddProvider, createSmartProspectProvider, createCosiallProvider, createTryKittProvider, createHunterProvider, createDropcontactProvider, createBouncerProvider, createBounceBanProvider, createSnovioProvider, findEmailWithTryKitt, verifyEmailWithTryKitt, verifyEmailWithBouncer, checkCatchAllDomain, verifyEmailsBatch, verifyEmailWithBounceBan, verifyEmailsBatchWithBounceBan, checkCatchAllWithBounceBan, findEmailsWithSnovio, verifyEmailWithSnovio, clearSnovioTokenCache, } from "./providers";
63
+ export { createConstructProvider, createLddProvider, createSmartProspectProvider, createCosiallProvider, createTryKittProvider, createHunterProvider, createBounceBanProvider, createSnovioProvider, findEmailWithTryKitt, verifyEmailWithTryKitt, verifyEmailWithBounceBan, verifyEmailsBatchWithBounceBan, checkCatchAllWithBounceBan, findEmailsWithSnovio, verifyEmailWithSnovio, clearSnovioTokenCache, } from "./providers";
66
64
  export { extractNumericLinkedInId } from "./providers/ldd";
67
65
  export { createSmartProspectClient, type SmartProspectClient, type SmartProspectLocationOptions, } from "./providers/smartprospect";
68
66
  export { enrichBusinessEmail, enrichBatch, enrichAllEmails, enrichAllBatch, } from "./orchestrator";
@@ -13,7 +13,7 @@
13
13
  * providers: {
14
14
  * ldd: { apiUrl: process.env.LDD_API_URL, apiToken: process.env.LDD_API_TOKEN },
15
15
  * smartprospect: { email: process.env.SMARTLEAD_EMAIL, password: process.env.SMARTLEAD_PASSWORD },
16
- * bouncer: { apiKey: process.env.BOUNCER_API_KEY },
16
+ * bounceban: { apiKey: process.env.BOUNCEBAN_API_KEY },
17
17
  * },
18
18
  * options: {
19
19
  * maxCostPerEmail: 0.05,
@@ -43,8 +43,8 @@ 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.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.checkCatchAllWithBounceBan = exports.verifyEmailsBatchWithBounceBan = exports.verifyEmailWithBounceBan = exports.verifyEmailsBatch = exports.checkCatchAllDomain = exports.verifyEmailWithBouncer = exports.verifyEmailWithTryKitt = exports.findEmailWithTryKitt = exports.createSnovioProvider = exports.createBounceBanProvider = exports.createBouncerProvider = exports.createDropcontactProvider = exports.createHunterProvider = exports.createTryKittProvider = 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 = exports.findBestMatch = exports.classifyMatchQuality = exports.calculateMatchConfidence = exports.clearFileCache = exports.isFileCacheEnabled = exports.disableFileCache = exports.enableFileCache = void 0;
46
+ 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.checkCatchAllWithBounceBan = exports.verifyEmailsBatchWithBounceBan = exports.verifyEmailWithBounceBan = exports.verifyEmailWithTryKitt = exports.findEmailWithTryKitt = exports.createSnovioProvider = exports.createBounceBanProvider = exports.createHunterProvider = exports.createTryKittProvider = 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 = exports.findBestMatch = exports.classifyMatchQuality = void 0;
48
48
  exports.createEnrichmentClient = createEnrichmentClient;
49
49
  const types_1 = require("./types");
50
50
  const orchestrator_1 = require("./orchestrator");
@@ -54,8 +54,6 @@ const smartprospect_1 = require("./providers/smartprospect");
54
54
  const cosiall_1 = require("./providers/cosiall");
55
55
  const trykitt_1 = require("./providers/trykitt");
56
56
  const hunter_1 = require("./providers/hunter");
57
- const dropcontact_1 = require("./providers/dropcontact");
58
- const bouncer_1 = require("./providers/bouncer");
59
57
  const bounceban_1 = require("./providers/bounceban");
60
58
  const snovio_1 = require("./providers/snovio");
61
59
  /**
@@ -74,10 +72,8 @@ const snovio_1 = require("./providers/snovio");
74
72
  * - bounceban: BounceBan catch-all verification (FREE single / $0.003 bulk)
75
73
  *
76
74
  * PHASE 3 - Paid finders (only if Phase 2 inconclusive):
77
- * - hunter: Hunter.io ($0.005/email)
75
+ * - hunter: Hunter.io ($0.015/email)
78
76
  * - snovio: Snov.io ($0.02/email)
79
- *
80
- * Note: bouncer ($0.006) and dropcontact ($0.01) available but not in default order
81
77
  */
82
78
  /**
83
79
  * Create an enrichment client with the given configuration
@@ -109,12 +105,6 @@ function createEnrichmentClient(config) {
109
105
  if (providerConfigs.hunter) {
110
106
  providerFuncs.set("hunter", (0, hunter_1.createHunterProvider)(providerConfigs.hunter));
111
107
  }
112
- if (providerConfigs.dropcontact) {
113
- providerFuncs.set("dropcontact", (0, dropcontact_1.createDropcontactProvider)(providerConfigs.dropcontact));
114
- }
115
- if (providerConfigs.bouncer) {
116
- providerFuncs.set("bouncer", (0, bouncer_1.createBouncerProvider)(providerConfigs.bouncer));
117
- }
118
108
  if (providerConfigs.snovio) {
119
109
  providerFuncs.set("snovio", (0, snovio_1.createSnovioProvider)(providerConfigs.snovio));
120
110
  }
@@ -287,17 +277,11 @@ Object.defineProperty(exports, "createSmartProspectProvider", { enumerable: true
287
277
  Object.defineProperty(exports, "createCosiallProvider", { enumerable: true, get: function () { return providers_1.createCosiallProvider; } });
288
278
  Object.defineProperty(exports, "createTryKittProvider", { enumerable: true, get: function () { return providers_1.createTryKittProvider; } });
289
279
  Object.defineProperty(exports, "createHunterProvider", { enumerable: true, get: function () { return providers_1.createHunterProvider; } });
290
- Object.defineProperty(exports, "createDropcontactProvider", { enumerable: true, get: function () { return providers_1.createDropcontactProvider; } });
291
- Object.defineProperty(exports, "createBouncerProvider", { enumerable: true, get: function () { return providers_1.createBouncerProvider; } });
292
280
  Object.defineProperty(exports, "createBounceBanProvider", { enumerable: true, get: function () { return providers_1.createBounceBanProvider; } });
293
281
  Object.defineProperty(exports, "createSnovioProvider", { enumerable: true, get: function () { return providers_1.createSnovioProvider; } });
294
282
  // TryKitt utilities
295
283
  Object.defineProperty(exports, "findEmailWithTryKitt", { enumerable: true, get: function () { return providers_1.findEmailWithTryKitt; } });
296
284
  Object.defineProperty(exports, "verifyEmailWithTryKitt", { enumerable: true, get: function () { return providers_1.verifyEmailWithTryKitt; } });
297
- // Bouncer utilities
298
- Object.defineProperty(exports, "verifyEmailWithBouncer", { enumerable: true, get: function () { return providers_1.verifyEmailWithBouncer; } });
299
- Object.defineProperty(exports, "checkCatchAllDomain", { enumerable: true, get: function () { return providers_1.checkCatchAllDomain; } });
300
- Object.defineProperty(exports, "verifyEmailsBatch", { enumerable: true, get: function () { return providers_1.verifyEmailsBatch; } });
301
285
  // BounceBan utilities
302
286
  Object.defineProperty(exports, "verifyEmailWithBounceBan", { enumerable: true, get: function () { return providers_1.verifyEmailWithBounceBan; } });
303
287
  Object.defineProperty(exports, "verifyEmailsBatchWithBounceBan", { enumerable: true, get: function () { return providers_1.verifyEmailsBatchWithBounceBan; } });
@@ -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" | "cosiall" | "trykitt" | "linkedin" | "pattern" | "hunter" | "bouncer" | "bounceban" | "snovio";
261
+ export type EmailSource = "ldd" | "smartprospect" | "cosiall" | "trykitt" | "linkedin" | "pattern" | "hunter" | "bounceban" | "snovio";
262
262
  /**
263
263
  * Email result from unified lookup
264
264
  */
@@ -327,10 +327,6 @@ export interface GetEmailsConfig {
327
327
  hunter?: {
328
328
  apiKey: string;
329
329
  };
330
- /** Bouncer configuration (PAID - SMTP verification) */
331
- bouncer?: {
332
- apiKey: string;
333
- };
334
330
  /** BounceBan configuration (FREE single / catch-all specialist) */
335
331
  bounceban?: {
336
332
  apiKey: string;
@@ -339,8 +335,8 @@ export interface GetEmailsConfig {
339
335
  };
340
336
  /** Snov.io configuration (PAID - email finder) */
341
337
  snovio?: {
342
- userId: string;
343
- apiSecret: string;
338
+ clientId: string;
339
+ clientSecret: string;
344
340
  };
345
341
  }
346
342
  /**
@@ -376,7 +372,7 @@ export interface GetEmailsOptions {
376
372
  * Strategy (optimized for FlexIQ):
377
373
  * 1. Query LDD + SmartProspect in PARALLEL (both FREE for you)
378
374
  * 2. Try email pattern guessing with MX verification (FREE)
379
- * 3. Only use Hunter/Apollo if confidence is below threshold (PAID - last resort)
375
+ * 3. Only use Hunter/Snov.io if confidence is below threshold (PAID - last resort)
380
376
  *
381
377
  * @example
382
378
  * ```ts
@@ -738,7 +738,7 @@ function createLinkedInEnricher(smartProspectConfig, defaultOptions = {}) {
738
738
  * Strategy (optimized for FlexIQ):
739
739
  * 1. Query LDD + SmartProspect in PARALLEL (both FREE for you)
740
740
  * 2. Try email pattern guessing with MX verification (FREE)
741
- * 3. Only use Hunter/Apollo if confidence is below threshold (PAID - last resort)
741
+ * 3. Only use Hunter/Snov.io if confidence is below threshold (PAID - last resort)
742
742
  *
743
743
  * @example
744
744
  * ```ts
@@ -813,12 +813,41 @@ async function getEmailsForLinkedInContact(contactOrLead, config, options = {})
813
813
  * Merge emails and boost confidence when found from multiple sources
814
814
  * - 2 sources: boost to at least 95% confidence
815
815
  * - 3+ sources: boost to 100% confidence
816
+ *
817
+ * IMPORTANT: When multiple sources find the same email, we pick the
818
+ * most authoritative source (not just highest confidence), then boost.
819
+ * Source priority: ldd > smartprospect > cosiall > trykitt > bounceban > hunter > snovio > pattern
816
820
  */
817
821
  const mergeAndBoostEmails = () => {
818
822
  const mergedEmails = [];
823
+ // Source priority for merging (lower = higher priority)
824
+ const mergePriority = {
825
+ ldd: 0, // Highest priority - your own data
826
+ smartprospect: 1, // Very reliable - verified database
827
+ cosiall: 2, // LinkedIn profile data
828
+ trykitt: 3, // AI-powered finder
829
+ bounceban: 4, // Verification provider
830
+ hunter: 5, // Paid finder
831
+ snovio: 6, // Paid finder
832
+ linkedin: 7, // Company lookup only
833
+ pattern: 8, // Pattern guessing - lowest priority
834
+ };
819
835
  for (const [_email, { results, sources }] of emailsByAddress) {
820
- // Find the best result (highest confidence)
821
- const bestResult = results.reduce((best, current) => current.confidence > best.confidence ? current : best);
836
+ // Find the best result by SOURCE PRIORITY first, then confidence
837
+ // This ensures SmartProspect/LDD are preferred over pattern guessing
838
+ const bestResult = results.reduce((best, current) => {
839
+ const bestPriority = mergePriority[best.source] ?? 99;
840
+ const currentPriority = mergePriority[current.source] ?? 99;
841
+ // Pick by source priority first
842
+ if (currentPriority < bestPriority) {
843
+ return current;
844
+ }
845
+ if (currentPriority > bestPriority) {
846
+ return best;
847
+ }
848
+ // Same source priority - pick higher confidence
849
+ return current.confidence > best.confidence ? current : best;
850
+ });
822
851
  // Boost confidence based on number of sources
823
852
  let boostedConfidence = bestResult.confidence;
824
853
  if (sources.size >= 3) {
@@ -874,90 +903,110 @@ async function getEmailsForLinkedInContact(contactOrLead, config, options = {})
874
903
  result.providersQueried.push("cosiall");
875
904
  freeProviderPromises.push(queryCosiall(contact, addEmail, result));
876
905
  }
877
- // TryKitt.ai lookup (FREE for individuals - unlimited)
878
- // AI-powered email finder with enterprise identity server catch-all verification
879
- if (!skipTryKitt && config.trykitt?.apiKey) {
880
- result.providersQueried.push("trykitt");
881
- freeProviderPromises.push(queryTryKitt(contact, config.trykitt, linkedInCompanyDomain || discoveredCompanyDomain, addEmail, result));
882
- }
906
+ // TryKitt.ai is moved to Phase 2 (after domain discovery)
907
+ // It needs name + domain to find emails
883
908
  // Wait for all Phase 1 free providers
884
909
  await Promise.all(freeProviderPromises);
885
- // Merge emails and calculate best confidence after Phase 1
910
+ // Merge emails after Phase 1
886
911
  result.emails = mergeAndBoostEmails();
887
- const phase1BestConfidence = result.emails.length > 0
888
- ? Math.max(...result.emails.map((e) => e.confidence))
889
- : 0;
890
912
  // ==========================================================================
891
- // Phase 2: Domain Discovery + Pattern Guessing
892
- // ONLY if Phase 1 confidence < threshold (to minimize LinkedIn API calls)
913
+ // Phase 2: Domain Discovery + FREE Email Finding (TryKitt, Pattern Guessing)
914
+ // FREE providers ALWAYS run - no reason to skip them
893
915
  // ==========================================================================
894
916
  let companyDomain = linkedInCompanyDomain || discoveredCompanyDomain;
895
- // Skip Phase 2 entirely if we already have high confidence emails
896
- if (phase1BestConfidence < paidProviderThreshold) {
897
- // Try LinkedIn Company Lookup for domain discovery (EXPENSIVE - uses LinkedIn API)
898
- // Only if we don't have a domain yet
899
- if (!companyDomain &&
900
- !skipPatternGuessing &&
901
- !options.skipLinkedInCompanyLookup &&
902
- config.linkedInCompanyLookup) {
903
- const companyUrn = contact.currentPositions?.[0]?.companyUrn;
904
- if (companyUrn) {
905
- const companyId = extractCompanyIdFromUrn(companyUrn);
906
- if (companyId) {
907
- result.providersQueried.push("linkedin");
908
- try {
909
- const company = await config.linkedInCompanyLookup(companyId);
910
- if (company?.websiteUrl) {
911
- companyDomain = extractDomainFromUrl(company.websiteUrl);
912
- if (companyDomain) {
913
- discoveredCompanyDomain = companyDomain;
914
- }
917
+ // Try LinkedIn Company Lookup for domain discovery
918
+ // Only if we don't have a domain yet
919
+ if (!companyDomain &&
920
+ !skipPatternGuessing &&
921
+ !options.skipLinkedInCompanyLookup &&
922
+ config.linkedInCompanyLookup) {
923
+ const companyUrn = contact.currentPositions?.[0]?.companyUrn;
924
+ if (companyUrn) {
925
+ const companyId = extractCompanyIdFromUrn(companyUrn);
926
+ if (companyId) {
927
+ result.providersQueried.push("linkedin");
928
+ try {
929
+ const company = await config.linkedInCompanyLookup(companyId);
930
+ if (company?.websiteUrl) {
931
+ companyDomain = extractDomainFromUrl(company.websiteUrl);
932
+ if (companyDomain) {
933
+ discoveredCompanyDomain = companyDomain;
915
934
  }
916
935
  }
917
- catch (err) {
918
- result.errors?.push(`LinkedIn: ${err instanceof Error ? err.message : "Company lookup failed"}`);
919
- }
936
+ }
937
+ catch (err) {
938
+ result.errors?.push(`LinkedIn: ${err instanceof Error ? err.message : "Company lookup failed"}`);
920
939
  }
921
940
  }
922
941
  }
923
- // Store discovered domain in result for visibility
924
- if (discoveredCompanyDomain && !linkedInCompanyDomain) {
925
- result.discoveredCompanyDomain = discoveredCompanyDomain;
926
- }
927
- // Run pattern guessing if we have a domain (from LinkedIn contact, SmartProspect, or LinkedIn API)
928
- if (!skipPatternGuessing && companyDomain) {
929
- result.providersQueried.push("pattern");
930
- await queryPatternGuessing(contact, companyDomain, addEmail, result);
931
- // Re-merge after pattern guessing
942
+ }
943
+ // Store discovered domain in result for visibility
944
+ if (discoveredCompanyDomain && !linkedInCompanyDomain) {
945
+ result.discoveredCompanyDomain = discoveredCompanyDomain;
946
+ }
947
+ // TryKitt.ai lookup (FREE for individuals - unlimited)
948
+ // AI-powered email finder - runs after domain discovery
949
+ if (!skipTryKitt && config.trykitt?.apiKey && companyDomain) {
950
+ result.providersQueried.push("trykitt");
951
+ await queryTryKitt(contact, config.trykitt, companyDomain, addEmail, result);
952
+ // Re-merge after TryKitt
953
+ result.emails = mergeAndBoostEmails();
954
+ }
955
+ // Run pattern guessing if we have a domain (from LinkedIn contact, SmartProspect, or LinkedIn API)
956
+ if (!skipPatternGuessing && companyDomain) {
957
+ result.providersQueried.push("pattern");
958
+ await queryPatternGuessing(contact, companyDomain, addEmail, result);
959
+ // Re-merge after pattern guessing
960
+ result.emails = mergeAndBoostEmails();
961
+ }
962
+ // ==========================================================================
963
+ // Check if we already have a verified BUSINESS email from a trusted source
964
+ // If so, skip BounceBan and paid providers entirely
965
+ //
966
+ // NOTE: LDD and Cosiall can return personal emails (Gmail, Yahoo, etc.)
967
+ // Don't stop searching if we only have personal emails - keep looking for business.
968
+ // ==========================================================================
969
+ const hasVerifiedBusinessEmail = result.emails.some((e) => e.verified &&
970
+ e.type === "business" &&
971
+ (e.source === "smartprospect" ||
972
+ e.source === "ldd" ||
973
+ e.source === "cosiall" ||
974
+ e.source === "trykitt"));
975
+ const phase2BestConfidence = result.emails.length > 0
976
+ ? Math.max(...result.emails.map((e) => e.confidence))
977
+ : 0;
978
+ // BounceBan catch-all verification (costs $0.008/lookup)
979
+ // SKIP if we already have a verified BUSINESS email from SmartProspect/Cosiall/TryKitt
980
+ // Only run to verify pattern-guessed emails when no trusted source found a business email
981
+ // NOTE: LDD personal emails don't count - we still want to find the business email
982
+ if (!skipBounceBan &&
983
+ config.bounceban?.apiKey &&
984
+ result.emails.length > 0 &&
985
+ !hasVerifiedBusinessEmail && // Skip if we have a verified BUSINESS email
986
+ phase2BestConfidence < paidProviderThreshold) {
987
+ // Only verify pattern-guessed emails (the ones we're uncertain about)
988
+ const emailsToVerify = result.emails.filter((e) => e.source === "pattern" && !e.verified);
989
+ if (emailsToVerify.length > 0) {
990
+ result.providersQueried.push("bounceban");
991
+ await queryBounceBan(emailsToVerify, config.bounceban, addEmail, result);
992
+ // Re-merge after BounceBan verification
932
993
  result.emails = mergeAndBoostEmails();
933
994
  }
934
- // BounceBan catch-all verification (FREE single emails)
935
- // Use BounceBan to verify pattern-guessed emails, especially on catch-all domains
936
- if (!skipBounceBan &&
937
- config.bounceban?.apiKey &&
938
- result.emails.length > 0) {
939
- // Only verify emails that are from pattern guessing or have low confidence
940
- const emailsToVerify = result.emails
941
- .filter((e) => e.source === "pattern" || (e.confidence < 80 && !e.verified))
942
- .slice(0, 3); // Verify top 3 to save credits
943
- if (emailsToVerify.length > 0) {
944
- result.providersQueried.push("bounceban");
945
- await queryBounceBan(emailsToVerify, config.bounceban, addEmail, result);
946
- // Re-merge after BounceBan verification
947
- result.emails = mergeAndBoostEmails();
948
- }
949
- }
950
995
  }
951
996
  // Calculate final best confidence
952
997
  const finalBestConfidence = result.emails.length > 0
953
998
  ? Math.max(...result.emails.map((e) => e.confidence))
954
999
  : 0;
955
1000
  // ==========================================================================
956
- // Phase 3: PAID providers as last resort (Hunter/Bouncer/Snovio)
1001
+ // Phase 3: PAID providers as LAST RESORT (Hunter then Snovio)
1002
+ // SKIP if we already have a verified BUSINESS email
1003
+ // NOTE: LDD personal emails don't count - we still want to find the business email
957
1004
  // ==========================================================================
958
- if (!skipPaidProviders && finalBestConfidence < paidProviderThreshold) {
959
- // Only use paid providers if we have low confidence or no results
960
- // Hunter.io - Email Finder ($0.005/lookup)
1005
+ const hasAnyVerifiedBusinessEmail = result.emails.some((e) => e.verified && e.type === "business");
1006
+ if (!skipPaidProviders &&
1007
+ !hasAnyVerifiedBusinessEmail && // Skip if we have a verified BUSINESS email
1008
+ finalBestConfidence < paidProviderThreshold) {
1009
+ // Hunter.io - Email Finder ($0.005/lookup) - try first
961
1010
  if (config.hunter?.apiKey) {
962
1011
  result.providersQueried.push("hunter");
963
1012
  // Extract linkedin handle from contact if available
@@ -980,8 +1029,15 @@ async function getEmailsForLinkedInContact(contactOrLead, config, options = {})
980
1029
  // Re-merge after Hunter
981
1030
  result.emails = mergeAndBoostEmails();
982
1031
  }
983
- // Snovio - Email Finder ($0.02/lookup) - best for catch-all domains
984
- if (config.snovio?.userId && config.snovio?.apiSecret && companyDomain) {
1032
+ // Check if Hunter found a verified email - if so, skip Snovio
1033
+ const hunterFoundVerified = result.emails.some((e) => e.source === "hunter" && e.verified);
1034
+ // Snovio - Email Finder ($0.02/lookup) - ONLY if Hunter didn't find anything
1035
+ // This is the absolute last resort since it's the most expensive
1036
+ if (config.snovio?.clientId &&
1037
+ config.snovio?.clientSecret &&
1038
+ companyDomain &&
1039
+ !hunterFoundVerified // Skip if Hunter already found a verified email
1040
+ ) {
985
1041
  result.providersQueried.push("snovio");
986
1042
  await querySnovio(contact, config.snovio, companyDomain, addEmail, result);
987
1043
  // Re-merge after Snovio
@@ -1004,8 +1060,7 @@ async function getEmailsForLinkedInContact(contactOrLead, config, options = {})
1004
1060
  pattern: 5,
1005
1061
  bounceban: 6, // BounceBan catch-all verification (FREE single)
1006
1062
  hunter: 7,
1007
- bouncer: 8,
1008
- snovio: 9,
1063
+ snovio: 8,
1009
1064
  };
1010
1065
  result.emails.sort((a, b) => {
1011
1066
  if (b.confidence !== a.confidence) {
@@ -1216,20 +1271,26 @@ async function queryBounceBan(emailsToVerify, bouncebanConfig, addEmail, result)
1216
1271
  useWaterfall: bouncebanConfig.useWaterfall ?? true,
1217
1272
  });
1218
1273
  if (verification && verification.status === "success") {
1219
- // Map BounceBan result to confidence
1220
- let confidence = verification.score;
1274
+ // BounceBan specializes in catch-all verification with 97%+ accuracy
1275
+ // Trust the score they return - don't artificially cap it
1276
+ const confidence = verification.score;
1221
1277
  const verified = verification.result === "deliverable";
1222
- // Reduce confidence for catch-all domains even if deliverable
1223
- if (verification.is_accept_all && verified) {
1224
- confidence = Math.min(confidence, 75);
1225
- }
1278
+ // For catch-all domains:
1279
+ // - "deliverable" = BounceBan's algorithm determined this specific email exists (97%+ accuracy)
1280
+ // - "risky" = Could not determine with high confidence
1281
+ // - "undeliverable" = Email definitely doesn't exist
1226
1282
  // Only add if it's potentially deliverable
1227
1283
  if (verification.result === "deliverable" ||
1228
1284
  verification.result === "risky") {
1285
+ // For "deliverable" on catch-all, BounceBan has verified this is the REAL email
1286
+ // For "risky", we keep lower confidence as BounceBan couldn't determine
1287
+ const finalConfidence = verification.result === "deliverable"
1288
+ ? Math.max(confidence, 85) // Deliverable = high confidence (BounceBan verified it)
1289
+ : Math.min(confidence, 60); // Risky = lower confidence
1229
1290
  addEmail({
1230
1291
  email: verification.email,
1231
1292
  source: "bounceban",
1232
- confidence,
1293
+ confidence: finalConfidence,
1233
1294
  type: "business",
1234
1295
  verified,
1235
1296
  isCatchAll: verification.is_accept_all,
@@ -1308,20 +1369,39 @@ async function querySmartProspect(contact, smartProspectConfig, minMatchConfiden
1308
1369
  .split("/")[0]
1309
1370
  .toLowerCase();
1310
1371
  }
1311
- // Fetch email for matched contact
1312
- const fetchResponse = await client.fetch([
1313
- matchResult.smartProspectContact.id,
1314
- ]);
1372
+ // Fetch email for matched contact (pass filter_id from search for polling)
1373
+ const searchFilterId = searchResponse.data.filter_id;
1374
+ const fetchResponse = await client.fetch([matchResult.smartProspectContact.id], searchFilterId);
1315
1375
  if (fetchResponse.success && fetchResponse.data.list.length > 0) {
1316
1376
  const enrichedContact = fetchResponse.data.list[0];
1317
1377
  if (enrichedContact.email) {
1378
+ // Calculate email confidence based on verification status AND match quality
1379
+ // SmartProspect verification status: "valid", "catch_all", "invalid", etc.
1380
+ const isVerified = enrichedContact.verificationStatus === "valid" ||
1381
+ enrichedContact.verificationStatus === "verified";
1382
+ const isCatchAll = enrichedContact.verificationStatus === "catch_all";
1383
+ // Base confidence from match quality (60-100%)
1384
+ let emailConfidence = matchResult.confidence;
1385
+ // Boost confidence for verified emails from SmartProspect
1386
+ // A verified email from SmartProspect is more reliable than pattern guessing
1387
+ if (isVerified) {
1388
+ emailConfidence = Math.max(emailConfidence, 90); // Verified = at least 90%
1389
+ }
1390
+ else if (isCatchAll) {
1391
+ emailConfidence = Math.max(emailConfidence, 80); // Catch-all = at least 80%
1392
+ }
1393
+ else if (enrichedContact.emailDeliverability > 0) {
1394
+ // Use deliverability score if available
1395
+ emailConfidence = Math.max(emailConfidence, enrichedContact.emailDeliverability * 100);
1396
+ }
1318
1397
  addEmail({
1319
1398
  email: enrichedContact.email,
1320
1399
  source: "smartprospect",
1321
- confidence: matchResult.confidence,
1400
+ confidence: emailConfidence,
1322
1401
  type: "business",
1323
- verified: enrichedContact.verificationStatus === "verified",
1402
+ verified: isVerified || isCatchAll,
1324
1403
  deliverability: enrichedContact.emailDeliverability,
1404
+ isCatchAll,
1325
1405
  metadata: {
1326
1406
  matchQuality: matchResult.quality,
1327
1407
  matchedFields: matchResult.matchedFields,
@@ -1329,6 +1409,7 @@ async function querySmartProspect(contact, smartProspectConfig, minMatchConfiden
1329
1409
  company: enrichedContact.company?.name,
1330
1410
  companyWebsite: companyWebsite,
1331
1411
  title: enrichedContact.title,
1412
+ verificationStatus: enrichedContact.verificationStatus,
1332
1413
  },
1333
1414
  });
1334
1415
  }
@@ -4,7 +4,7 @@
4
4
  * Runs providers in sequence until a verified email is found that meets
5
5
  * the confidence threshold. Supports budget tracking and cost callbacks.
6
6
  */
7
- import type { CanonicalEmail, EnrichmentCandidate, ProviderFunc, BatchEnrichmentOptions, CostCallback, EnrichmentLogger, MultiEmailResult } from './types';
7
+ import type { CanonicalEmail, EnrichmentCandidate, ProviderFunc, BatchEnrichmentOptions, CostCallback, EnrichmentLogger, MultiEmailResult } from "./types";
8
8
  export interface OrchestratorOptions {
9
9
  /** Provider functions in order */
10
10
  providers: ProviderFunc[];