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.
- package/README.md +6 -49
- package/dist/enrichment/index.d.ts +3 -5
- package/dist/enrichment/index.js +4 -20
- package/dist/enrichment/matching.d.ts +4 -8
- package/dist/enrichment/matching.js +165 -84
- package/dist/enrichment/orchestrator.d.ts +1 -1
- package/dist/enrichment/orchestrator.js +51 -44
- package/dist/enrichment/providers/bounceban.js +19 -10
- package/dist/enrichment/providers/construct.js +97 -89
- package/dist/enrichment/providers/cosiall.js +2 -2
- package/dist/enrichment/providers/hunter.js +2 -1
- package/dist/enrichment/providers/index.d.ts +0 -2
- package/dist/enrichment/providers/index.js +1 -8
- package/dist/enrichment/providers/ldd.js +4 -2
- package/dist/enrichment/providers/smartprospect.d.ts +5 -2
- package/dist/enrichment/providers/smartprospect.js +64 -27
- package/dist/enrichment/providers/snovio.d.ts +4 -4
- package/dist/enrichment/providers/snovio.js +21 -17
- package/dist/enrichment/providers/trykitt.d.ts +2 -2
- package/dist/enrichment/providers/trykitt.js +86 -21
- package/dist/enrichment/types.d.ts +8 -71
- package/dist/enrichment/types.js +3 -8
- package/dist/enrichment/utils/rate-limiter.js +0 -2
- package/dist/index.d.ts +2 -4
- package/dist/index.js +2 -2
- package/docs/ENRICHMENT.md +3 -45
- package/docs/INTEGRATION.md +1 -3
- package/docs/PLAYGROUND.md +4 -9
- package/docs/api/assets/hierarchy.js +1 -1
- package/docs/api/assets/navigation.js +1 -1
- package/docs/api/assets/search.js +1 -1
- package/docs/api/classes/LinkedInClientError.html +4 -4
- package/docs/api/functions/_testGetAccountCookies.html +2 -2
- package/docs/api/functions/_testGetAccountEntry.html +2 -2
- package/docs/api/functions/_testGetAllAccountIds.html +2 -2
- package/docs/api/functions/_testGetPoolState.html +2 -2
- package/docs/api/functions/adminResetAccount.html +1 -1
- package/docs/api/functions/adminSetCooldown.html +1 -1
- package/docs/api/functions/buildCookieHeader.html +1 -1
- package/docs/api/functions/clearAllSmartLeadTokens.html +2 -2
- package/docs/api/functions/clearRequestHistory.html +1 -1
- package/docs/api/functions/clearSessionAccount.html +1 -1
- package/docs/api/functions/clearSmartLeadToken.html +2 -2
- package/docs/api/functions/createEnrichmentClient.html +3 -3
- package/docs/api/functions/extractCsrfToken.html +1 -1
- package/docs/api/functions/extractLinkedInHandle.html +2 -2
- package/docs/api/functions/fetchCookiesFromCosiall.html +2 -2
- package/docs/api/functions/fetchProfileEmailsFromCosiall.html +2 -2
- package/docs/api/functions/forceRefreshCookies.html +1 -1
- package/docs/api/functions/getAccountForSession.html +1 -1
- package/docs/api/functions/getAccountsSummary.html +1 -1
- package/docs/api/functions/getCompaniesBatch.html +2 -2
- package/docs/api/functions/getCompanyById.html +2 -2
- package/docs/api/functions/getCompanyByUrl.html +1 -1
- package/docs/api/functions/getConfig.html +1 -1
- package/docs/api/functions/getCookiePoolHealth.html +1 -1
- package/docs/api/functions/getProfileByUrn.html +2 -2
- package/docs/api/functions/getProfileByVanity.html +2 -2
- package/docs/api/functions/getProfilesBatch.html +1 -1
- package/docs/api/functions/getRequestHistory.html +1 -1
- package/docs/api/functions/getSalesNavigatorProfileDetails.html +1 -1
- package/docs/api/functions/getSalesNavigatorProfileFull.html +2 -2
- package/docs/api/functions/getSmartLeadToken.html +1 -1
- package/docs/api/functions/getSmartLeadTokenCacheStats.html +2 -2
- package/docs/api/functions/getSmartLeadUser.html +2 -2
- package/docs/api/functions/getSnapshot.html +1 -1
- package/docs/api/functions/getYearsAtCompanyOptions.html +2 -2
- package/docs/api/functions/getYearsInPositionOptions.html +2 -2
- package/docs/api/functions/getYearsOfExperienceOptions.html +2 -2
- package/docs/api/functions/incrementMetric.html +1 -1
- package/docs/api/functions/initializeCookiePool.html +1 -1
- package/docs/api/functions/initializeLinkedInClient.html +1 -1
- package/docs/api/functions/isBusinessEmail.html +2 -2
- package/docs/api/functions/isDisposableDomain.html +2 -2
- package/docs/api/functions/isDisposableEmail.html +2 -2
- package/docs/api/functions/isPersonalDomain.html +2 -2
- package/docs/api/functions/isPersonalEmail.html +2 -2
- package/docs/api/functions/isRoleAccount.html +2 -2
- package/docs/api/functions/isValidEmailSyntax.html +2 -2
- package/docs/api/functions/parseFullProfile.html +2 -2
- package/docs/api/functions/parseSalesSearchResults.html +1 -1
- package/docs/api/functions/reportAccountFailure.html +1 -1
- package/docs/api/functions/reportAccountSuccess.html +1 -1
- package/docs/api/functions/resolveCompanyUniversalName.html +1 -1
- package/docs/api/functions/searchSalesLeads.html +2 -2
- package/docs/api/functions/selectAccountForRequest.html +1 -1
- package/docs/api/functions/setAccountForSession.html +1 -1
- package/docs/api/functions/typeahead.html +1 -1
- package/docs/api/functions/verifyEmailMx.html +1 -1
- package/docs/api/hierarchy.html +1 -1
- package/docs/api/index.html +3 -3
- package/docs/api/interfaces/AccountCookies.html +2 -2
- package/docs/api/interfaces/BatchEnrichmentOptions.html +8 -8
- package/docs/api/interfaces/CacheAdapter.html +4 -4
- package/docs/api/interfaces/CanonicalEmail.html +8 -8
- package/docs/api/interfaces/Company.html +2 -2
- package/docs/api/interfaces/ConstructConfig.html +5 -5
- package/docs/api/interfaces/CosiallProfileEmailsResponse.html +6 -6
- package/docs/api/interfaces/EnrichmentCandidate.html +4 -4
- package/docs/api/interfaces/EnrichmentClient.html +6 -6
- package/docs/api/interfaces/EnrichmentClientConfig.html +7 -7
- package/docs/api/interfaces/EnrichmentLogger.html +3 -3
- package/docs/api/interfaces/EnrichmentOptions.html +6 -6
- package/docs/api/interfaces/HunterConfig.html +3 -3
- package/docs/api/interfaces/LddConfig.html +3 -3
- package/docs/api/interfaces/LddProfileData.html +2 -2
- package/docs/api/interfaces/LinkedInClientConfig.html +2 -2
- package/docs/api/interfaces/LinkedInCookie.html +2 -2
- package/docs/api/interfaces/LinkedInPosition.html +2 -2
- package/docs/api/interfaces/LinkedInProfile.html +2 -2
- package/docs/api/interfaces/LinkedInSpotlightBadge.html +2 -2
- package/docs/api/interfaces/LinkedInTenure.html +2 -2
- package/docs/api/interfaces/Metrics.html +2 -2
- package/docs/api/interfaces/MetricsSnapshot.html +2 -2
- package/docs/api/interfaces/ProfileEducation.html +2 -2
- package/docs/api/interfaces/ProfileEmailsLookupOptions.html +5 -5
- package/docs/api/interfaces/ProfilePosition.html +2 -2
- package/docs/api/interfaces/ProfileSkill.html +2 -2
- package/docs/api/interfaces/ProviderResult.html +6 -6
- package/docs/api/interfaces/ProvidersConfig.html +6 -9
- package/docs/api/interfaces/RequestHistoryEntry.html +2 -2
- package/docs/api/interfaces/SalesLeadSearchResult.html +2 -2
- package/docs/api/interfaces/SalesNavigatorContactInfo.html +2 -2
- package/docs/api/interfaces/SalesNavigatorPosition.html +2 -2
- package/docs/api/interfaces/SalesNavigatorProfile.html +2 -2
- package/docs/api/interfaces/SalesNavigatorProfileFull.html +4 -4
- package/docs/api/interfaces/SearchSalesResult.html +2 -2
- package/docs/api/interfaces/SmartLeadAuthConfig.html +4 -4
- package/docs/api/interfaces/SmartLeadCredentials.html +2 -2
- package/docs/api/interfaces/SmartLeadLoginResponse.html +2 -2
- package/docs/api/interfaces/SmartLeadUser.html +2 -2
- package/docs/api/interfaces/SmartProspectConfig.html +8 -8
- package/docs/api/interfaces/SmartProspectContact.html +2 -2
- package/docs/api/interfaces/SmartProspectSearchFilters.html +21 -21
- package/docs/api/interfaces/TypeaheadItem.html +2 -2
- package/docs/api/interfaces/TypeaheadResult.html +2 -2
- package/docs/api/interfaces/VerificationResult.html +9 -9
- package/docs/api/types/CostCallback.html +2 -2
- package/docs/api/types/Geo.html +2 -2
- package/docs/api/types/LddApiResponse.html +1 -1
- package/docs/api/types/ProviderFunc.html +2 -2
- package/docs/api/types/ProviderName.html +2 -2
- package/docs/api/types/SalesSearchFilters.html +2 -2
- package/docs/api/types/TypeaheadType.html +1 -1
- package/docs/api/variables/COMPANY_SIZE_OPTIONS.html +1 -1
- package/docs/api/variables/DEFAULT_PROVIDER_ORDER.html +3 -4
- package/docs/api/variables/DISPOSABLE_DOMAINS.html +2 -2
- package/docs/api/variables/FUNCTION_OPTIONS.html +1 -1
- package/docs/api/variables/INDUSTRY_OPTIONS.html +1 -1
- package/docs/api/variables/LANGUAGE_OPTIONS.html +1 -1
- package/docs/api/variables/PERSONAL_DOMAINS.html +2 -2
- package/docs/api/variables/PROVIDER_COSTS.html +3 -5
- package/docs/api/variables/REGION_OPTIONS.html +1 -1
- package/docs/api/variables/SENIORITY_OPTIONS.html +2 -2
- package/docs/api/variables/YEARS_OPTIONS.html +1 -1
- package/package.json +1 -1
- package/dist/enrichment/providers/apollo.d.ts +0 -11
- package/dist/enrichment/providers/apollo.js +0 -181
- package/dist/enrichment/providers/bouncer.d.ts +0 -67
- package/dist/enrichment/providers/bouncer.js +0 -231
- package/dist/enrichment/providers/dropcontact.d.ts +0 -22
- package/dist/enrichment/providers/dropcontact.js +0 -206
- 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 (
|
|
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
|
-
| **
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
*
|
|
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.
|
|
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,
|
|
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";
|
package/dist/enrichment/index.js
CHANGED
|
@@ -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
|
-
*
|
|
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.
|
|
47
|
-
exports.salesLeadToContact = exports.getEmailsForLinkedInContactsBatch = exports.getEmailsForLinkedInContact = exports.createLinkedInEnricher = exports.enrichLinkedInContactsBatch = exports.enrichLinkedInContact = exports.parseLinkedInSearchResponse = exports.buildSmartProspectFiltersFromLinkedIn = exports.matchContacts = exports.findBestMatch = exports.classifyMatchQuality =
|
|
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.
|
|
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" | "
|
|
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
|
-
|
|
343
|
-
|
|
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/
|
|
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/
|
|
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
|
|
821
|
-
|
|
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
|
|
878
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
896
|
-
if
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
const
|
|
904
|
-
if (
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
if (
|
|
911
|
-
|
|
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
|
-
|
|
918
|
-
|
|
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
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
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
|
|
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
|
-
|
|
959
|
-
|
|
960
|
-
//
|
|
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
|
-
//
|
|
984
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
1220
|
-
|
|
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
|
-
//
|
|
1223
|
-
|
|
1224
|
-
|
|
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
|
|
1313
|
-
|
|
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:
|
|
1400
|
+
confidence: emailConfidence,
|
|
1322
1401
|
type: "business",
|
|
1323
|
-
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
|
|
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[];
|