diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 6f07869ffb..81dbe58d94 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -2283,6 +2283,49 @@ export function ElevenLabsIcon(props: SVGProps) { ) } +export function FindymailIcon(props: SVGProps) { + return ( + + + + + + + + + + + + + + + + ) +} export function FathomIcon(props: SVGProps) { return ( diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 0cb344dafc..0de540e9e9 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -58,6 +58,7 @@ import { ExtendIcon, EyeIcon, FathomIcon, + FindymailIcon, FirecrawlIcon, FirefliesIcon, GammaIcon, @@ -262,7 +263,9 @@ export const blockTypeToIconMap: Record = { extend_v2: ExtendIcon, fathom: FathomIcon, file: DocumentIcon, + file_v3: DocumentIcon, file_v4: DocumentIcon, + findymail: FindymailIcon, firecrawl: FirecrawlIcon, fireflies: FirefliesIcon, fireflies_v2: FirefliesIcon, diff --git a/apps/docs/content/docs/en/tools/findymail.mdx b/apps/docs/content/docs/en/tools/findymail.mdx new file mode 100644 index 0000000000..f0a226324d --- /dev/null +++ b/apps/docs/content/docs/en/tools/findymail.mdx @@ -0,0 +1,286 @@ +--- +title: Findymail +description: Find and verify B2B emails, phones, employees, and company data +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[Findymail](https://findymail.com/) is a B2B contact data platform for finding and verifying work emails, phone numbers, and enriched profile data on company employees. It combines real-time email finding, deliverability verification, reverse-lookup, and technology stack detection in a single API. + +With Findymail, you can: + +- **Find work emails by name and company:** Resolve a verified work email from a person's name plus a company domain or company name. +- **Find emails from LinkedIn:** Look up the verified work email behind any LinkedIn profile URL. +- **Find contacts by role:** Search a company domain for verified emails matching specific target roles (e.g., CEO, Founder). +- **Verify deliverability:** Check whether an email is deliverable and identify the underlying mail provider. +- **Reverse-lookup profiles:** Given an email, return the matching LinkedIn URL and an optional enriched profile (job, education, skills, certificates). +- **Enrich companies and employees:** Look up company metadata by LinkedIn URL, domain, or name, and find employees by website and target job titles. +- **Find phone numbers:** Retrieve a contact's phone number (US-only) from a LinkedIn profile URL. +- **Detect technology stacks:** Search the technology catalog or look up the full tech stack of a company by domain. + +In Sim, the Findymail integration lets your agents programmatically build verified contact lists, enrich CRMs, qualify leads, and gather technographic data without leaving your workflow. Use it to automate outbound prospecting, augment incoming form submissions, validate email captures before sending, and trigger downstream actions when a verified contact is found. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Integrate Findymail to find verified work emails by name, domain, or LinkedIn URL, verify deliverability, reverse-lookup profiles from emails, enrich company data, find employees by job title, look up phone numbers, search technology stacks, and check credit usage. + + + +## Tools + +### `findymail_verify_email` + +Verifies the deliverability of an email address. Uses one verifier credit. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `email` | string | Yes | Email address to verify \(e.g., john@example.com\) | +| `apiKey` | string | Yes | Findymail API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `email` | string | The verified email address | +| `verified` | boolean | Whether the email is verified as deliverable | +| `provider` | string | Email service provider \(e.g., Google, Microsoft\) | + +### `findymail_find_email_from_name` + +Find someone + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `name` | string | Yes | Person's full name \(e.g., 'John Doe'\) | +| `domain` | string | Yes | Company domain \(preferred\) or company name \(e.g., stripe.com\) | +| `apiKey` | string | Yes | Findymail API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `contact` | object | Contact information | +| ↳ `name` | string | Contact full name | +| ↳ `email` | string | Contact email address | +| ↳ `domain` | string | Email domain | + +### `findymail_find_emails_by_domain` + +Find verified contacts at a given domain matching one or more target roles (max 3 roles). Limited to 5 concurrent synchronous requests. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `domain` | string | Yes | Company domain \(e.g., stripe.com\) | +| `roles` | array | Yes | Target roles at the company \(max 3, e.g., \["CEO", "Founder"\]\) | +| `items` | string | No | No description | +| `apiKey` | string | Yes | Findymail API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `contacts` | array | List of contacts found | +| ↳ `name` | string | Contact full name | +| ↳ `email` | string | Contact email address | +| ↳ `domain` | string | Email domain | + +### `findymail_find_email_from_linkedin` + +Find someone + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `linkedin_url` | string | Yes | Person's LinkedIn URL or username \(e.g., 'https://linkedin.com/in/johndoe' or 'johndoe'\) | +| `apiKey` | string | Yes | Findymail API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `contact` | object | Contact information | +| ↳ `name` | string | Contact full name | +| ↳ `email` | string | Contact email address | +| ↳ `domain` | string | Email domain | + +### `findymail_reverse_email_lookup` + +Find a business profile from an email address. Uses 1 finder credit if a profile is found, 2 credits if returning full profile data. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `email` | string | Yes | Work or personal email address to look up | +| `with_profile` | boolean | No | Whether to return enriched profile metadata \(default: false\) | +| `apiKey` | string | Yes | Findymail API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `email` | string | The email address that was looked up | +| `linkedin_url` | string | LinkedIn profile URL | +| `fullName` | string | Full name from profile | +| `username` | string | LinkedIn username | +| `headline` | string | Profile headline | +| `jobTitle` | string | Current job title | +| `summary` | string | Profile summary | +| `city` | string | City | +| `region` | string | Region or state | +| `country` | string | Country | +| `companyLinkedinUrl` | string | Current company LinkedIn URL | +| `companyName` | string | Current company name | +| `companyWebsite` | string | Current company website | +| `isPremium` | boolean | Whether the profile has LinkedIn Premium | +| `isOpenProfile` | boolean | Whether the profile is an Open Profile | +| `skills` | array | List of profile skills | +| `jobs` | array | Job history entries | +| `educations` | array | Education history \(school, degree, fieldOfStudy, startDate, endDate\) | +| `certificates` | array | Certifications \(name, issuingOrganization, issueDate, expirationDate\) | + +### `findymail_get_company` + +Retrieve company information from a LinkedIn URL, domain, or company name. Uses 1 finder credit per successful response. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `linkedin_url` | string | No | Company LinkedIn URL \(e.g., https://www.linkedin.com/company/stripe/\) | +| `domain` | string | No | Company domain \(e.g., stripe.com\) | +| `name` | string | No | Company name \(e.g., Stripe\) | +| `apiKey` | string | Yes | Findymail API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `name` | string | Company name | +| `domain` | string | Company domain | +| `company_size` | string | Employee headcount range \(e.g., 1001-5000\) | +| `industry` | string | Industry classification | +| `linkedin_url` | string | Company LinkedIn URL | +| `description` | string | Company description | + +### `findymail_find_employees` + +Find employees at a company by website and target job titles. Uses 1 credit per found contact. Does not return email addresses. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `website` | string | Yes | Company website or domain \(e.g., google.com\) | +| `job_titles` | array | Yes | Target job titles to search for \(max 10, e.g., \["Software Engineer", "CEO"\]\) | +| `items` | string | No | No description | +| `count` | number | No | Number of contacts to return \(max 5, default 1\) | +| `apiKey` | string | Yes | Findymail API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `employees` | array | List of employees matching the search criteria | +| ↳ `name` | string | Employee full name | +| ↳ `linkedinUrl` | string | LinkedIn profile URL | +| ↳ `companyWebsite` | string | Company website | +| ↳ `companyName` | string | Company name | +| ↳ `jobTitle` | string | Job title | + +### `findymail_find_phone` + +Find someone + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `linkedin_url` | string | Yes | Person's LinkedIn URL or username \(e.g., 'https://linkedin.com/in/johndoe' or 'johndoe'\) | +| `apiKey` | string | Yes | Findymail API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `phone` | string | Phone number in E.164 format. Only available for US numbers. | +| `line_type` | string | Phone line type \(e.g., "Mobile", "Landline"\) | + +### `findymail_search_technologies` + +Search the technology catalog by name. Returns up to 25 technologies. Free endpoint, rate limited to 10 requests per minute. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `q` | string | Yes | Search term \(min 2 characters, e.g., "React"\) | +| `apiKey` | string | Yes | Findymail API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `technologies` | array | List of technologies | +| ↳ `name` | string | Technology name | +| ↳ `category` | string | Technology category | +| ↳ `subcategory` | string | Technology subcategory | +| ↳ `last_detected_at` | string | Last detection timestamp \(ISO 8601\) | + +### `findymail_lookup_technologies` + +Get the technology stack for a company by domain. Optionally filter by technology names. 1 finder credit if technologies are found, free otherwise. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `domain` | string | Yes | Company domain to look up \(e.g., stripe.com\) | +| `technologies` | array | No | Filter by technology names, case-insensitive \(e.g., \["React", "TypeScript"\]\) | +| `items` | string | No | No description | +| `apiKey` | string | Yes | Findymail API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `technologies` | array | List of technologies | +| ↳ `name` | string | Technology name | +| ↳ `category` | string | Technology category | +| ↳ `subcategory` | string | Technology subcategory | +| ↳ `last_detected_at` | string | Last detection timestamp \(ISO 8601\) | +| `domain` | string | The resolved company domain | + +### `findymail_get_credits` + +Retrieve the remaining finder and verifier credits for the authenticated account. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Findymail API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `credits` | number | Remaining finder credits | +| `verifier_credits` | number | Remaining verifier credits | + + diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index e3fd30b75e..5f04f102bb 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -53,6 +53,7 @@ "extend", "fathom", "file", + "findymail", "firecrawl", "fireflies", "gamma", diff --git a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts index dbae80a253..64fe8a8556 100644 --- a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts +++ b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts @@ -58,6 +58,7 @@ import { ExtendIcon, EyeIcon, FathomIcon, + FindymailIcon, FirecrawlIcon, FirefliesIcon, GammaIcon, @@ -259,6 +260,7 @@ export const blockTypeToIconMap: Record = { extend_v2: ExtendIcon, fathom: FathomIcon, file_v4: DocumentIcon, + findymail: FindymailIcon, firecrawl: FirecrawlIcon, fireflies_v2: FirefliesIcon, gamma: GammaIcon, diff --git a/apps/sim/app/(landing)/integrations/data/integrations.json b/apps/sim/app/(landing)/integrations/data/integrations.json index 9d33239f91..d67628c4ab 100644 --- a/apps/sim/app/(landing)/integrations/data/integrations.json +++ b/apps/sim/app/(landing)/integrations/data/integrations.json @@ -4074,6 +4074,69 @@ "integrationTypes": ["file-storage", "documents"], "tags": ["document-processing"] }, + { + "type": "findymail", + "slug": "findymail", + "name": "Findymail", + "description": "Find and verify B2B emails, phones, employees, and company data", + "longDescription": "Integrate Findymail to find verified work emails by name, domain, or LinkedIn URL, verify deliverability, reverse-lookup profiles from emails, enrich company data, find employees by job title, look up phone numbers, search technology stacks, and check credit usage.", + "bgColor": "#FFFFFF", + "iconName": "FindymailIcon", + "docsUrl": "https://docs.sim.ai/tools/findymail", + "operations": [ + { + "name": "Find Email From Name", + "description": "Find someone" + }, + { + "name": "Find Email From LinkedIn", + "description": "Find someone" + }, + { + "name": "Find Emails By Domain", + "description": "Find verified contacts at a given domain matching one or more target roles (max 3 roles). Limited to 5 concurrent synchronous requests." + }, + { + "name": "Verify Email", + "description": "Verifies the deliverability of an email address. Uses one verifier credit." + }, + { + "name": "Reverse Email Lookup", + "description": "Find a business profile from an email address. Uses 1 finder credit if a profile is found, 2 credits if returning full profile data." + }, + { + "name": "Get Company Info", + "description": "Retrieve company information from a LinkedIn URL, domain, or company name. Uses 1 finder credit per successful response." + }, + { + "name": "Find Employees", + "description": "Find employees at a company by website and target job titles. Uses 1 credit per found contact. Does not return email addresses." + }, + { + "name": "Find Phone", + "description": "Find someone" + }, + { + "name": "Search Technologies", + "description": "Search the technology catalog by name. Returns up to 25 technologies. Free endpoint, rate limited to 10 requests per minute." + }, + { + "name": "Lookup Technologies By Domain", + "description": "Get the technology stack for a company by domain. Optionally filter by technology names. 1 finder credit if technologies are found, free otherwise." + }, + { + "name": "Get Remaining Credits", + "description": "Retrieve the remaining finder and verifier credits for the authenticated account." + } + ], + "operationCount": 11, + "triggers": [], + "triggerCount": 0, + "authType": "api-key", + "category": "tools", + "integrationTypes": ["sales"], + "tags": ["enrichment", "sales-engagement"] + }, { "type": "firecrawl", "slug": "firecrawl", diff --git a/apps/sim/blocks/blocks/findymail.ts b/apps/sim/blocks/blocks/findymail.ts new file mode 100644 index 0000000000..d5f11bc743 --- /dev/null +++ b/apps/sim/blocks/blocks/findymail.ts @@ -0,0 +1,345 @@ +import { FindymailIcon } from '@/components/icons' +import { AuthMode, type BlockConfig, IntegrationType } from '@/blocks/types' +import type { FindymailResponse } from '@/tools/findymail/types' + +export const FindymailBlock: BlockConfig = { + type: 'findymail', + name: 'Findymail', + description: 'Find and verify B2B emails, phones, employees, and company data', + authMode: AuthMode.ApiKey, + longDescription: + 'Integrate Findymail to find verified work emails by name, domain, or LinkedIn URL, verify deliverability, reverse-lookup profiles from emails, enrich company data, find employees by job title, look up phone numbers, search technology stacks, and check credit usage.', + docsLink: 'https://docs.sim.ai/tools/findymail', + category: 'tools', + integrationType: IntegrationType.Sales, + tags: ['enrichment', 'sales-engagement'], + bgColor: '#FFFFFF', + icon: FindymailIcon, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Find Email From Name', id: 'findymail_find_email_from_name' }, + { label: 'Find Email From LinkedIn', id: 'findymail_find_email_from_linkedin' }, + { label: 'Find Emails By Domain', id: 'findymail_find_emails_by_domain' }, + { label: 'Verify Email', id: 'findymail_verify_email' }, + { label: 'Reverse Email Lookup', id: 'findymail_reverse_email_lookup' }, + { label: 'Get Company Info', id: 'findymail_get_company' }, + { label: 'Find Employees', id: 'findymail_find_employees' }, + { label: 'Find Phone', id: 'findymail_find_phone' }, + { label: 'Search Technologies', id: 'findymail_search_technologies' }, + { label: 'Lookup Technologies By Domain', id: 'findymail_lookup_technologies' }, + { label: 'Get Remaining Credits', id: 'findymail_get_credits' }, + ], + value: () => 'findymail_find_email_from_name', + }, + // Find Email From Name + { + id: 'name', + title: 'Full Name', + type: 'short-input', + required: true, + placeholder: 'John Doe', + condition: { field: 'operation', value: 'findymail_find_email_from_name' }, + }, + { + id: 'domain', + title: 'Company Domain or Name', + type: 'short-input', + required: true, + placeholder: 'stripe.com', + condition: { field: 'operation', value: 'findymail_find_email_from_name' }, + }, + // Find Email From LinkedIn + { + id: 'linkedin_url', + title: 'LinkedIn URL', + type: 'short-input', + required: true, + placeholder: 'https://linkedin.com/in/johndoe', + condition: { field: 'operation', value: 'findymail_find_email_from_linkedin' }, + }, + // Find Emails By Domain + { + id: 'domain', + title: 'Domain', + type: 'short-input', + required: true, + placeholder: 'stripe.com', + condition: { field: 'operation', value: 'findymail_find_emails_by_domain' }, + }, + { + id: 'roles', + title: 'Target Roles', + type: 'long-input', + required: true, + placeholder: '["CEO", "Founder"]', + condition: { field: 'operation', value: 'findymail_find_emails_by_domain' }, + }, + // Verify Email + { + id: 'email', + title: 'Email Address', + type: 'short-input', + required: true, + placeholder: 'john@example.com', + condition: { field: 'operation', value: 'findymail_verify_email' }, + }, + // Reverse Email Lookup + { + id: 'email', + title: 'Email Address', + type: 'short-input', + required: true, + placeholder: 'john@example.com', + condition: { field: 'operation', value: 'findymail_reverse_email_lookup' }, + }, + { + id: 'with_profile', + title: 'Return Full Profile', + type: 'switch', + condition: { field: 'operation', value: 'findymail_reverse_email_lookup' }, + mode: 'advanced', + }, + // Get Company Info — provide at least one of LinkedIn URL, domain, or name + { + id: 'linkedin_url', + title: 'Company LinkedIn URL', + type: 'short-input', + placeholder: 'LinkedIn URL (at least one of URL/domain/name required)', + condition: { field: 'operation', value: 'findymail_get_company' }, + }, + { + id: 'domain', + title: 'Company Domain', + type: 'short-input', + placeholder: 'stripe.com (at least one of URL/domain/name required)', + condition: { field: 'operation', value: 'findymail_get_company' }, + }, + { + id: 'name', + title: 'Company Name', + type: 'short-input', + placeholder: 'Stripe (at least one of URL/domain/name required)', + condition: { field: 'operation', value: 'findymail_get_company' }, + }, + // Find Employees + { + id: 'website', + title: 'Company Website', + type: 'short-input', + required: true, + placeholder: 'google.com', + condition: { field: 'operation', value: 'findymail_find_employees' }, + }, + { + id: 'job_titles', + title: 'Job Titles', + type: 'long-input', + required: true, + placeholder: '["Software Engineer", "CEO"]', + condition: { field: 'operation', value: 'findymail_find_employees' }, + }, + { + id: 'count', + title: 'Number of Contacts', + type: 'short-input', + placeholder: '1', + condition: { field: 'operation', value: 'findymail_find_employees' }, + mode: 'advanced', + }, + // Find Phone + { + id: 'linkedin_url', + title: 'LinkedIn URL', + type: 'short-input', + required: true, + placeholder: 'https://linkedin.com/in/johndoe', + condition: { field: 'operation', value: 'findymail_find_phone' }, + }, + // Search Technologies + { + id: 'q', + title: 'Search Term', + type: 'short-input', + required: true, + placeholder: 'React', + condition: { field: 'operation', value: 'findymail_search_technologies' }, + }, + // Lookup Technologies By Domain + { + id: 'domain', + title: 'Company Domain', + type: 'short-input', + required: true, + placeholder: 'stripe.com', + condition: { field: 'operation', value: 'findymail_lookup_technologies' }, + }, + { + id: 'technologies', + title: 'Filter Technologies', + type: 'long-input', + placeholder: '["React", "TypeScript"]', + condition: { field: 'operation', value: 'findymail_lookup_technologies' }, + mode: 'advanced', + }, + // API Key + { + id: 'apiKey', + title: 'API Key', + type: 'short-input', + required: true, + placeholder: 'Enter your Findymail API key', + password: true, + }, + ], + tools: { + access: [ + 'findymail_verify_email', + 'findymail_find_email_from_name', + 'findymail_find_emails_by_domain', + 'findymail_find_email_from_linkedin', + 'findymail_reverse_email_lookup', + 'findymail_get_company', + 'findymail_find_employees', + 'findymail_find_phone', + 'findymail_search_technologies', + 'findymail_lookup_technologies', + 'findymail_get_credits', + ], + config: { + tool: (params) => { + switch (params.operation) { + case 'findymail_verify_email': + case 'findymail_find_email_from_name': + case 'findymail_find_emails_by_domain': + case 'findymail_find_email_from_linkedin': + case 'findymail_reverse_email_lookup': + case 'findymail_get_company': + case 'findymail_find_employees': + case 'findymail_find_phone': + case 'findymail_search_technologies': + case 'findymail_lookup_technologies': + case 'findymail_get_credits': + return params.operation + default: + return 'findymail_find_email_from_name' + } + }, + params: (params) => { + const { operation: _operation, ...rest } = params + const result: Record = {} + for (const [key, value] of Object.entries(rest)) { + if (value === undefined || value === null || value === '') continue + if (key === 'count') { + const n = Number(value) + if (!Number.isNaN(n)) result[key] = n + } else if (key === 'roles' || key === 'job_titles' || key === 'technologies') { + if (Array.isArray(value)) { + result[key] = value + } else if (typeof value === 'string') { + const trimmed = value.trim() + if (trimmed.startsWith('[')) { + try { + const parsed = JSON.parse(trimmed) + if (Array.isArray(parsed)) { + result[key] = parsed + continue + } + } catch { + // fall through to comma-split + } + } + result[key] = trimmed + .split(',') + .map((s) => s.trim()) + .filter(Boolean) + } + } else { + result[key] = value + } + } + return result + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + apiKey: { type: 'string', description: 'Findymail API key' }, + email: { type: 'string', description: 'Email address' }, + name: { type: 'string', description: 'Full name or company name' }, + domain: { type: 'string', description: 'Company domain' }, + linkedin_url: { type: 'string', description: 'LinkedIn profile or company URL' }, + roles: { type: 'array', description: 'Target roles (max 3)' }, + with_profile: { type: 'boolean', description: 'Return full profile data on reverse lookup' }, + website: { type: 'string', description: 'Company website for employee search' }, + job_titles: { type: 'array', description: 'Target job titles (max 10)' }, + count: { type: 'number', description: 'Number of contacts to return (max 5)' }, + q: { type: 'string', description: 'Technology search query' }, + technologies: { type: 'array', description: 'Technology names to filter by' }, + }, + outputs: { + // Verify Email + verified: { type: 'boolean', description: 'Whether the email is deliverable' }, + provider: { type: 'string', description: 'Email service provider' }, + // Find / Reverse / Company + contact: { + type: 'json', + description: 'Contact found (name, email, domain)', + }, + contacts: { + type: 'array', + description: 'Contacts found at the domain (name, email, domain)', + }, + linkedin_url: { type: 'string', description: 'LinkedIn URL' }, + fullName: { type: 'string', description: 'Full name from LinkedIn profile' }, + username: { type: 'string', description: 'LinkedIn username' }, + headline: { type: 'string', description: 'Profile headline' }, + jobTitle: { type: 'string', description: 'Job title' }, + summary: { type: 'string', description: 'Profile summary' }, + city: { type: 'string', description: 'City' }, + region: { type: 'string', description: 'Region/state' }, + country: { type: 'string', description: 'Country' }, + companyLinkedinUrl: { type: 'string', description: 'Current company LinkedIn URL' }, + companyName: { type: 'string', description: 'Current company name' }, + companyWebsite: { type: 'string', description: 'Current company website' }, + isPremium: { type: 'boolean', description: 'Whether the profile has LinkedIn Premium' }, + isOpenProfile: { type: 'boolean', description: 'Whether the profile is open' }, + skills: { type: 'array', description: 'Profile skills' }, + jobs: { type: 'array', description: 'Job history entries' }, + educations: { + type: 'array', + description: 'Education history (school, degree, fieldOfStudy, startDate, endDate)', + }, + certificates: { + type: 'array', + description: 'Certifications (name, issuingOrganization, issueDate, expirationDate)', + }, + // Get Company + name: { type: 'string', description: 'Company name (get-company)' }, + domain: { type: 'string', description: 'Company domain' }, + company_size: { type: 'string', description: 'Headcount range (e.g., 1001-5000)' }, + industry: { type: 'string', description: 'Industry classification' }, + description: { type: 'string', description: 'Company description' }, + // Find Employees + employees: { + type: 'array', + description: 'Employees found (name, linkedinUrl, companyWebsite, companyName, jobTitle)', + }, + // Find Phone + phone: { type: 'string', description: 'Phone number in E.164 format (US only)' }, + line_type: { type: 'string', description: 'Phone line type (Mobile, Landline)' }, + // Technologies + technologies: { + type: 'array', + description: 'Technologies (name, category, subcategory, last_detected_at)', + }, + // Get Credits + credits: { type: 'number', description: 'Remaining finder credits' }, + verifier_credits: { type: 'number', description: 'Remaining verifier credits' }, + // Reverse / Verify shared + email: { type: 'string', description: 'Email address' }, + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index 2038e1f279..268e010543 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -56,6 +56,7 @@ import { ExaBlock } from '@/blocks/blocks/exa' import { ExtendBlock, ExtendV2Block } from '@/blocks/blocks/extend' import { FathomBlock } from '@/blocks/blocks/fathom' import { FileBlock, FileV2Block, FileV3Block, FileV4Block } from '@/blocks/blocks/file' +import { FindymailBlock } from '@/blocks/blocks/findymail' import { FirecrawlBlock } from '@/blocks/blocks/firecrawl' import { FirefliesBlock, FirefliesV2Block } from '@/blocks/blocks/fireflies' import { FunctionBlock } from '@/blocks/blocks/function' @@ -301,6 +302,7 @@ export const registry: Record = { file_v2: FileV2Block, file_v3: FileV3Block, file_v4: FileV4Block, + findymail: FindymailBlock, firecrawl: FirecrawlBlock, fireflies: FirefliesBlock, fireflies_v2: FirefliesV2Block, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 6f07869ffb..81dbe58d94 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -2283,6 +2283,49 @@ export function ElevenLabsIcon(props: SVGProps) { ) } +export function FindymailIcon(props: SVGProps) { + return ( + + + + + + + + + + + + + + + + ) +} export function FathomIcon(props: SVGProps) { return ( diff --git a/apps/sim/tools/findymail/find_email_from_linkedin.ts b/apps/sim/tools/findymail/find_email_from_linkedin.ts new file mode 100644 index 0000000000..8193035fc4 --- /dev/null +++ b/apps/sim/tools/findymail/find_email_from_linkedin.ts @@ -0,0 +1,75 @@ +import type { + FindymailFindEmailFromLinkedInParams, + FindymailFindEmailFromLinkedInResponse, +} from '@/tools/findymail/types' +import { FINDYMAIL_CONTACT_OUTPUT } from '@/tools/findymail/types' +import type { ToolConfig } from '@/tools/types' + +export const findEmailFromLinkedInTool: ToolConfig< + FindymailFindEmailFromLinkedInParams, + FindymailFindEmailFromLinkedInResponse +> = { + id: 'findymail_find_email_from_linkedin', + name: 'Findymail Find Email From LinkedIn', + description: + "Find someone's email from a LinkedIn profile URL or username. Uses one finder credit when a verified email is found.", + version: '1.0.0', + + params: { + linkedin_url: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + "Person's LinkedIn URL or username (e.g., 'https://linkedin.com/in/johndoe' or 'johndoe')", + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Findymail API Key', + }, + }, + + request: { + url: 'https://app.findymail.com/api/search/business-profile', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + body: (params) => ({ linkedin_url: params.linkedin_url }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + return { + success: false, + error: + (errorData as Record).message || + `Findymail API error: ${response.status} ${response.statusText}`, + output: { contact: null }, + } + } + const data = await response.json() + const contact = data.contact ?? data.payload?.contact ?? null + return { + success: true, + output: { + contact: contact + ? { + name: contact.name ?? '', + email: contact.email ?? '', + domain: contact.domain ?? '', + } + : null, + }, + } + }, + + outputs: { + contact: { ...FINDYMAIL_CONTACT_OUTPUT, optional: true }, + }, +} diff --git a/apps/sim/tools/findymail/find_email_from_name.ts b/apps/sim/tools/findymail/find_email_from_name.ts new file mode 100644 index 0000000000..048a885e38 --- /dev/null +++ b/apps/sim/tools/findymail/find_email_from_name.ts @@ -0,0 +1,80 @@ +import type { + FindymailFindEmailFromNameParams, + FindymailFindEmailFromNameResponse, +} from '@/tools/findymail/types' +import { FINDYMAIL_CONTACT_OUTPUT } from '@/tools/findymail/types' +import type { ToolConfig } from '@/tools/types' + +export const findEmailFromNameTool: ToolConfig< + FindymailFindEmailFromNameParams, + FindymailFindEmailFromNameResponse +> = { + id: 'findymail_find_email_from_name', + name: 'Findymail Find Email From Name', + description: + "Find someone's email from their name and a company domain or company name. Uses one finder credit when a verified email is found.", + version: '1.0.0', + + params: { + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: "Person's full name (e.g., 'John Doe')", + }, + domain: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Company domain (preferred) or company name (e.g., stripe.com)', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Findymail API Key', + }, + }, + + request: { + url: 'https://app.findymail.com/api/search/name', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + body: (params) => ({ name: params.name, domain: params.domain }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + return { + success: false, + error: + (errorData as Record).message || + `Findymail API error: ${response.status} ${response.statusText}`, + output: { contact: null }, + } + } + const data = await response.json() + const contact = data.contact ?? data.payload?.contact ?? null + return { + success: true, + output: { + contact: contact + ? { + name: contact.name ?? '', + email: contact.email ?? '', + domain: contact.domain ?? '', + } + : null, + }, + } + }, + + outputs: { + contact: { ...FINDYMAIL_CONTACT_OUTPUT, optional: true }, + }, +} diff --git a/apps/sim/tools/findymail/find_emails_by_domain.ts b/apps/sim/tools/findymail/find_emails_by_domain.ts new file mode 100644 index 0000000000..5c726c0184 --- /dev/null +++ b/apps/sim/tools/findymail/find_emails_by_domain.ts @@ -0,0 +1,77 @@ +import type { + FindymailFindEmailsByDomainParams, + FindymailFindEmailsByDomainResponse, +} from '@/tools/findymail/types' +import { FINDYMAIL_CONTACTS_OUTPUT } from '@/tools/findymail/types' +import type { ToolConfig } from '@/tools/types' + +export const findEmailsByDomainTool: ToolConfig< + FindymailFindEmailsByDomainParams, + FindymailFindEmailsByDomainResponse +> = { + id: 'findymail_find_emails_by_domain', + name: 'Findymail Find Emails By Domain', + description: + 'Find verified contacts at a given domain matching one or more target roles (max 3 roles). Limited to 5 concurrent synchronous requests.', + version: '1.0.0', + + params: { + domain: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Company domain (e.g., stripe.com)', + }, + roles: { + type: 'array', + required: true, + visibility: 'user-or-llm', + description: 'Target roles at the company (max 3, e.g., ["CEO", "Founder"])', + items: { type: 'string' }, + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Findymail API Key', + }, + }, + + request: { + url: 'https://app.findymail.com/api/search/domain', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + body: (params) => ({ domain: params.domain, roles: params.roles }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + return { + success: false, + error: + (errorData as Record).message || + `Findymail API error: ${response.status} ${response.statusText}`, + output: { contacts: [] }, + } + } + const data = await response.json() + const raw = data.contacts ?? data.payload?.contacts ?? [] + const contacts = Array.isArray(raw) + ? raw.map((c: { name?: string; email?: string; domain?: string }) => ({ + name: c.name ?? '', + email: c.email ?? '', + domain: c.domain ?? '', + })) + : [] + return { success: true, output: { contacts } } + }, + + outputs: { + contacts: FINDYMAIL_CONTACTS_OUTPUT, + }, +} diff --git a/apps/sim/tools/findymail/find_employees.ts b/apps/sim/tools/findymail/find_employees.ts new file mode 100644 index 0000000000..8cfcf208b5 --- /dev/null +++ b/apps/sim/tools/findymail/find_employees.ts @@ -0,0 +1,100 @@ +import type { + FindymailFindEmployeesParams, + FindymailFindEmployeesResponse, +} from '@/tools/findymail/types' +import { FINDYMAIL_EMPLOYEES_OUTPUT } from '@/tools/findymail/types' +import type { ToolConfig } from '@/tools/types' + +export const findEmployeesTool: ToolConfig< + FindymailFindEmployeesParams, + FindymailFindEmployeesResponse +> = { + id: 'findymail_find_employees', + name: 'Findymail Find Employees', + description: + 'Find employees at a company by website and target job titles. Uses 1 credit per found contact. Does not return email addresses.', + version: '1.0.0', + + params: { + website: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Company website or domain (e.g., google.com)', + }, + job_titles: { + type: 'array', + required: true, + visibility: 'user-or-llm', + description: 'Target job titles to search for (max 10, e.g., ["Software Engineer", "CEO"])', + items: { type: 'string' }, + }, + count: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of contacts to return (max 5, default 1)', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Findymail API Key', + }, + }, + + request: { + url: 'https://app.findymail.com/api/search/employees', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + body: (params) => { + const body: Record = { + website: params.website, + job_titles: params.job_titles, + } + if (params.count !== undefined) body.count = params.count + return body + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + return { + success: false, + error: + (errorData as Record).message || + `Findymail API error: ${response.status} ${response.statusText}`, + output: { employees: [] }, + } + } + const data = await response.json() + const raw = Array.isArray(data) ? data : (data.data ?? []) + const employees = Array.isArray(raw) + ? raw.map( + (e: { + name?: string + linkedinUrl?: string + companyWebsite?: string + companyName?: string + jobTitle?: string + }) => ({ + name: e.name ?? '', + linkedinUrl: e.linkedinUrl ?? null, + companyWebsite: e.companyWebsite ?? null, + companyName: e.companyName ?? null, + jobTitle: e.jobTitle ?? null, + }) + ) + : [] + return { success: true, output: { employees } } + }, + + outputs: { + employees: FINDYMAIL_EMPLOYEES_OUTPUT, + }, +} diff --git a/apps/sim/tools/findymail/find_phone.ts b/apps/sim/tools/findymail/find_phone.ts new file mode 100644 index 0000000000..6405401335 --- /dev/null +++ b/apps/sim/tools/findymail/find_phone.ts @@ -0,0 +1,71 @@ +import type { FindymailFindPhoneParams, FindymailFindPhoneResponse } from '@/tools/findymail/types' +import type { ToolConfig } from '@/tools/types' + +export const findPhoneTool: ToolConfig = { + id: 'findymail_find_phone', + name: 'Findymail Find Phone', + description: + "Find someone's phone number from a LinkedIn profile URL. Uses 10 finder credits if a phone is found. EU citizens are excluded for legal reasons.", + version: '1.0.0', + + params: { + linkedin_url: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + "Person's LinkedIn URL or username (e.g., 'https://linkedin.com/in/johndoe' or 'johndoe')", + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Findymail API Key', + }, + }, + + request: { + url: 'https://app.findymail.com/api/search/phone', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + body: (params) => ({ linkedin_url: params.linkedin_url }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + return { + success: false, + error: + (errorData as Record).message || + `Findymail API error: ${response.status} ${response.statusText}`, + output: { phone: null, line_type: null }, + } + } + const data = await response.json() + return { + success: true, + output: { + phone: data.phone ?? null, + line_type: data.line_type ?? null, + }, + } + }, + + outputs: { + phone: { + type: 'string', + description: 'Phone number in E.164 format. Only available for US numbers.', + optional: true, + }, + line_type: { + type: 'string', + description: 'Phone line type (e.g., "Mobile", "Landline")', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/findymail/get_company.ts b/apps/sim/tools/findymail/get_company.ts new file mode 100644 index 0000000000..7a1c10cb24 --- /dev/null +++ b/apps/sim/tools/findymail/get_company.ts @@ -0,0 +1,102 @@ +import type { + FindymailGetCompanyParams, + FindymailGetCompanyResponse, +} from '@/tools/findymail/types' +import type { ToolConfig } from '@/tools/types' + +export const getCompanyTool: ToolConfig = { + id: 'findymail_get_company', + name: 'Findymail Get Company', + description: + 'Retrieve company information from a LinkedIn URL, domain, or company name. Uses 1 finder credit per successful response.', + version: '1.0.0', + + params: { + linkedin_url: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Company LinkedIn URL (e.g., https://www.linkedin.com/company/stripe/)', + }, + domain: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Company domain (e.g., stripe.com)', + }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Company name (e.g., Stripe)', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Findymail API Key', + }, + }, + + request: { + url: 'https://app.findymail.com/api/search/company', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + body: (params) => { + const body: Record = {} + if (params.linkedin_url) body.linkedin_url = params.linkedin_url + if (params.domain) body.domain = params.domain + if (params.name) body.name = params.name + return body + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + return { + success: false, + error: + (errorData as Record).message || + `Findymail API error: ${response.status} ${response.statusText}`, + output: { + name: null, + domain: null, + company_size: null, + industry: null, + linkedin_url: null, + description: null, + }, + } + } + const data = await response.json() + return { + success: true, + output: { + name: data.name ?? null, + domain: data.domain ?? null, + company_size: data.company_size ?? null, + industry: data.industry ?? null, + linkedin_url: data.linkedin_url ?? null, + description: data.description ?? null, + }, + } + }, + + outputs: { + name: { type: 'string', description: 'Company name', optional: true }, + domain: { type: 'string', description: 'Company domain', optional: true }, + company_size: { + type: 'string', + description: 'Employee headcount range (e.g., 1001-5000)', + optional: true, + }, + industry: { type: 'string', description: 'Industry classification', optional: true }, + linkedin_url: { type: 'string', description: 'Company LinkedIn URL', optional: true }, + description: { type: 'string', description: 'Company description', optional: true }, + }, +} diff --git a/apps/sim/tools/findymail/get_credits.ts b/apps/sim/tools/findymail/get_credits.ts new file mode 100644 index 0000000000..1bdfef5f0b --- /dev/null +++ b/apps/sim/tools/findymail/get_credits.ts @@ -0,0 +1,56 @@ +import type { + FindymailGetCreditsParams, + FindymailGetCreditsResponse, +} from '@/tools/findymail/types' +import type { ToolConfig } from '@/tools/types' + +export const getCreditsTool: ToolConfig = { + id: 'findymail_get_credits', + name: 'Findymail Get Credits', + description: 'Retrieve the remaining finder and verifier credits for the authenticated account.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Findymail API Key', + }, + }, + + request: { + url: 'https://app.findymail.com/api/credits', + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + Accept: 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + return { + success: false, + error: + (errorData as Record).message || + `Findymail API error: ${response.status} ${response.statusText}`, + output: { credits: 0, verifier_credits: 0 }, + } + } + const data = await response.json() + return { + success: true, + output: { + credits: data.credits ?? 0, + verifier_credits: data.verifier_credits ?? 0, + }, + } + }, + + outputs: { + credits: { type: 'number', description: 'Remaining finder credits' }, + verifier_credits: { type: 'number', description: 'Remaining verifier credits' }, + }, +} diff --git a/apps/sim/tools/findymail/index.ts b/apps/sim/tools/findymail/index.ts new file mode 100644 index 0000000000..759ee4c5f0 --- /dev/null +++ b/apps/sim/tools/findymail/index.ts @@ -0,0 +1,23 @@ +import { findEmailFromLinkedInTool } from '@/tools/findymail/find_email_from_linkedin' +import { findEmailFromNameTool } from '@/tools/findymail/find_email_from_name' +import { findEmailsByDomainTool } from '@/tools/findymail/find_emails_by_domain' +import { findEmployeesTool } from '@/tools/findymail/find_employees' +import { findPhoneTool } from '@/tools/findymail/find_phone' +import { getCompanyTool } from '@/tools/findymail/get_company' +import { getCreditsTool } from '@/tools/findymail/get_credits' +import { lookupTechnologiesTool } from '@/tools/findymail/lookup_technologies' +import { reverseEmailLookupTool } from '@/tools/findymail/reverse_email_lookup' +import { searchTechnologiesTool } from '@/tools/findymail/search_technologies' +import { verifyEmailTool } from '@/tools/findymail/verify_email' + +export const findymailVerifyEmailTool = verifyEmailTool +export const findymailFindEmailFromNameTool = findEmailFromNameTool +export const findymailFindEmailsByDomainTool = findEmailsByDomainTool +export const findymailFindEmailFromLinkedInTool = findEmailFromLinkedInTool +export const findymailReverseEmailLookupTool = reverseEmailLookupTool +export const findymailGetCompanyTool = getCompanyTool +export const findymailFindEmployeesTool = findEmployeesTool +export const findymailFindPhoneTool = findPhoneTool +export const findymailSearchTechnologiesTool = searchTechnologiesTool +export const findymailLookupTechnologiesTool = lookupTechnologiesTool +export const findymailGetCreditsTool = getCreditsTool diff --git a/apps/sim/tools/findymail/lookup_technologies.ts b/apps/sim/tools/findymail/lookup_technologies.ts new file mode 100644 index 0000000000..a662f220eb --- /dev/null +++ b/apps/sim/tools/findymail/lookup_technologies.ts @@ -0,0 +1,98 @@ +import type { + FindymailLookupTechnologiesParams, + FindymailLookupTechnologiesResponse, +} from '@/tools/findymail/types' +import { FINDYMAIL_TECHNOLOGIES_OUTPUT } from '@/tools/findymail/types' +import type { ToolConfig } from '@/tools/types' + +export const lookupTechnologiesTool: ToolConfig< + FindymailLookupTechnologiesParams, + FindymailLookupTechnologiesResponse +> = { + id: 'findymail_lookup_technologies', + name: 'Findymail Lookup Technologies', + description: + 'Get the technology stack for a company by domain. Optionally filter by technology names. 1 finder credit if technologies are found, free otherwise.', + version: '1.0.0', + + params: { + domain: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Company domain to look up (e.g., stripe.com)', + }, + technologies: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Filter by technology names, case-insensitive (e.g., ["React", "TypeScript"])', + items: { type: 'string' }, + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Findymail API Key', + }, + }, + + request: { + url: 'https://app.findymail.com/api/technologies', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + body: (params) => { + const body: Record = { domain: params.domain } + if (params.technologies && params.technologies.length > 0) { + body.technologies = params.technologies + } + return body + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + return { + success: false, + error: + (errorData as Record).message || + `Findymail API error: ${response.status} ${response.statusText}`, + output: { domain: '', technologies: [] }, + } + } + const data = await response.json() + const raw = data.technologies ?? [] + const technologies = Array.isArray(raw) + ? raw.map( + (t: { + name?: string + category?: string + subcategory?: string + last_detected_at?: string + }) => ({ + name: t.name ?? '', + category: t.category ?? null, + subcategory: t.subcategory ?? null, + last_detected_at: t.last_detected_at ?? null, + }) + ) + : [] + return { + success: true, + output: { + domain: data.domain ?? '', + technologies, + }, + } + }, + + outputs: { + domain: { type: 'string', description: 'The resolved company domain' }, + technologies: FINDYMAIL_TECHNOLOGIES_OUTPUT, + }, +} diff --git a/apps/sim/tools/findymail/reverse_email_lookup.ts b/apps/sim/tools/findymail/reverse_email_lookup.ts new file mode 100644 index 0000000000..3808a3a632 --- /dev/null +++ b/apps/sim/tools/findymail/reverse_email_lookup.ts @@ -0,0 +1,149 @@ +import type { + FindymailReverseEmailLookupParams, + FindymailReverseEmailLookupResponse, +} from '@/tools/findymail/types' +import type { ToolConfig } from '@/tools/types' + +export const reverseEmailLookupTool: ToolConfig< + FindymailReverseEmailLookupParams, + FindymailReverseEmailLookupResponse +> = { + id: 'findymail_reverse_email_lookup', + name: 'Findymail Reverse Email Lookup', + description: + 'Find a business profile from an email address. Uses 1 finder credit if a profile is found, 2 credits if returning full profile data.', + version: '1.0.0', + + params: { + email: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Work or personal email address to look up', + }, + with_profile: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to return enriched profile metadata (default: false)', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Findymail API Key', + }, + }, + + request: { + url: 'https://app.findymail.com/api/search/reverse-email', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + body: (params) => ({ + email: params.email, + ...(params.with_profile ? { with_profile: true } : {}), + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + return { + success: false, + error: + (errorData as Record).message || + `Findymail API error: ${response.status} ${response.statusText}`, + output: { + email: null, + linkedin_url: null, + fullName: null, + username: null, + headline: null, + jobTitle: null, + summary: null, + city: null, + region: null, + country: null, + companyLinkedinUrl: null, + companyName: null, + companyWebsite: null, + isPremium: null, + isOpenProfile: null, + skills: [], + jobs: [], + educations: [], + certificates: [], + }, + } + } + const data = await response.json() + return { + success: true, + output: { + email: data.email ?? null, + linkedin_url: data.linkedin_url ?? null, + fullName: data.fullName ?? null, + username: data.username ?? null, + headline: data.headline ?? null, + jobTitle: data.jobTitle ?? null, + summary: data.summary ?? null, + city: data.city ?? null, + region: data.region ?? null, + country: data.country ?? null, + companyLinkedinUrl: data.companyLinkedinUrl ?? null, + companyName: data.companyName ?? null, + companyWebsite: data.companyWebsite ?? null, + isPremium: data.isPremium ?? null, + isOpenProfile: data.isOpenProfile ?? null, + skills: data.skills ?? [], + jobs: data.jobs ?? [], + educations: data.educations ?? [], + certificates: data.certificates ?? [], + }, + } + }, + + outputs: { + email: { type: 'string', description: 'The email address that was looked up', optional: true }, + linkedin_url: { type: 'string', description: 'LinkedIn profile URL', optional: true }, + fullName: { type: 'string', description: 'Full name from profile', optional: true }, + username: { type: 'string', description: 'LinkedIn username', optional: true }, + headline: { type: 'string', description: 'Profile headline', optional: true }, + jobTitle: { type: 'string', description: 'Current job title', optional: true }, + summary: { type: 'string', description: 'Profile summary', optional: true }, + city: { type: 'string', description: 'City', optional: true }, + region: { type: 'string', description: 'Region or state', optional: true }, + country: { type: 'string', description: 'Country', optional: true }, + companyLinkedinUrl: { + type: 'string', + description: 'Current company LinkedIn URL', + optional: true, + }, + companyName: { type: 'string', description: 'Current company name', optional: true }, + companyWebsite: { type: 'string', description: 'Current company website', optional: true }, + isPremium: { + type: 'boolean', + description: 'Whether the profile has LinkedIn Premium', + optional: true, + }, + isOpenProfile: { + type: 'boolean', + description: 'Whether the profile is an Open Profile', + optional: true, + }, + skills: { type: 'array', description: 'List of profile skills' }, + jobs: { type: 'array', description: 'Job history entries' }, + educations: { + type: 'array', + description: 'Education history (school, degree, fieldOfStudy, startDate, endDate)', + }, + certificates: { + type: 'array', + description: 'Certifications (name, issuingOrganization, issueDate, expirationDate)', + }, + }, +} diff --git a/apps/sim/tools/findymail/search_technologies.ts b/apps/sim/tools/findymail/search_technologies.ts new file mode 100644 index 0000000000..1c168c7b35 --- /dev/null +++ b/apps/sim/tools/findymail/search_technologies.ts @@ -0,0 +1,80 @@ +import type { + FindymailSearchTechnologiesParams, + FindymailSearchTechnologiesResponse, +} from '@/tools/findymail/types' +import { FINDYMAIL_TECHNOLOGIES_OUTPUT } from '@/tools/findymail/types' +import type { ToolConfig } from '@/tools/types' + +export const searchTechnologiesTool: ToolConfig< + FindymailSearchTechnologiesParams, + FindymailSearchTechnologiesResponse +> = { + id: 'findymail_search_technologies', + name: 'Findymail Search Technologies', + description: + 'Search the technology catalog by name. Returns up to 25 technologies. Free endpoint, rate limited to 10 requests per minute.', + version: '1.0.0', + + params: { + q: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Search term (min 2 characters, e.g., "React")', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Findymail API Key', + }, + }, + + request: { + url: (params) => { + const url = new URL('https://app.findymail.com/api/technologies/search') + url.searchParams.append('q', params.q) + return url.toString() + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + Accept: 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + return { + success: false, + error: + (errorData as Record).message || + `Findymail API error: ${response.status} ${response.statusText}`, + output: { technologies: [] }, + } + } + const data = await response.json() + const raw = data.data ?? [] + const technologies = Array.isArray(raw) + ? raw.map( + (t: { + name?: string + category?: string + subcategory?: string + last_detected_at?: string + }) => ({ + name: t.name ?? '', + category: t.category ?? null, + subcategory: t.subcategory ?? null, + last_detected_at: t.last_detected_at ?? null, + }) + ) + : [] + return { success: true, output: { technologies } } + }, + + outputs: { + technologies: FINDYMAIL_TECHNOLOGIES_OUTPUT, + }, +} diff --git a/apps/sim/tools/findymail/types.ts b/apps/sim/tools/findymail/types.ts new file mode 100644 index 0000000000..72ceab1c79 --- /dev/null +++ b/apps/sim/tools/findymail/types.ts @@ -0,0 +1,241 @@ +import type { OutputProperty, ToolResponse } from '@/tools/types' + +interface FindymailBaseParams { + apiKey: string +} + +export const FINDYMAIL_CONTACT_OUTPUT_PROPERTIES = { + name: { type: 'string', description: 'Contact full name' }, + email: { type: 'string', description: 'Contact email address' }, + domain: { type: 'string', description: 'Email domain' }, +} as const satisfies Record + +export const FINDYMAIL_CONTACT_OUTPUT: OutputProperty = { + type: 'object', + description: 'Contact information', + properties: FINDYMAIL_CONTACT_OUTPUT_PROPERTIES, +} + +export const FINDYMAIL_CONTACTS_OUTPUT: OutputProperty = { + type: 'array', + description: 'List of contacts found', + items: { + type: 'object', + properties: FINDYMAIL_CONTACT_OUTPUT_PROPERTIES, + }, +} + +export const FINDYMAIL_TECHNOLOGY_OUTPUT_PROPERTIES = { + name: { type: 'string', description: 'Technology name' }, + category: { type: 'string', description: 'Technology category' }, + subcategory: { type: 'string', description: 'Technology subcategory' }, + last_detected_at: { + type: 'string', + description: 'Last detection timestamp (ISO 8601)', + optional: true, + }, +} as const satisfies Record + +export const FINDYMAIL_TECHNOLOGIES_OUTPUT: OutputProperty = { + type: 'array', + description: 'List of technologies', + items: { + type: 'object', + properties: FINDYMAIL_TECHNOLOGY_OUTPUT_PROPERTIES, + }, +} + +export const FINDYMAIL_EMPLOYEE_OUTPUT_PROPERTIES = { + name: { type: 'string', description: 'Employee full name' }, + linkedinUrl: { type: 'string', description: 'LinkedIn profile URL', optional: true }, + companyWebsite: { type: 'string', description: 'Company website', optional: true }, + companyName: { type: 'string', description: 'Company name', optional: true }, + jobTitle: { type: 'string', description: 'Job title', optional: true }, +} as const satisfies Record + +export const FINDYMAIL_EMPLOYEES_OUTPUT: OutputProperty = { + type: 'array', + description: 'List of employees matching the search criteria', + items: { + type: 'object', + properties: FINDYMAIL_EMPLOYEE_OUTPUT_PROPERTIES, + }, +} + +export interface FindymailContact { + name: string + email: string + domain: string +} + +export interface FindymailVerifyEmailParams extends FindymailBaseParams { + email: string +} + +export interface FindymailVerifyEmailResponse extends ToolResponse { + output: { + email: string + verified: boolean + provider: string | null + } +} + +export interface FindymailFindEmailFromNameParams extends FindymailBaseParams { + name: string + domain: string +} + +export interface FindymailFindEmailFromNameResponse extends ToolResponse { + output: { + contact: FindymailContact | null + } +} + +export interface FindymailFindEmailsByDomainParams extends FindymailBaseParams { + domain: string + roles: string[] +} + +export interface FindymailFindEmailsByDomainResponse extends ToolResponse { + output: { + contacts: FindymailContact[] + } +} + +export interface FindymailFindEmailFromLinkedInParams extends FindymailBaseParams { + linkedin_url: string +} + +export interface FindymailFindEmailFromLinkedInResponse extends ToolResponse { + output: { + contact: FindymailContact | null + } +} + +export interface FindymailReverseEmailLookupParams extends FindymailBaseParams { + email: string + with_profile?: boolean +} + +export interface FindymailReverseEmailLookupResponse extends ToolResponse { + output: { + email: string | null + linkedin_url: string | null + fullName: string | null + username: string | null + headline: string | null + jobTitle: string | null + summary: string | null + city: string | null + region: string | null + country: string | null + companyLinkedinUrl: string | null + companyName: string | null + companyWebsite: string | null + isPremium: boolean | null + isOpenProfile: boolean | null + skills: unknown[] + jobs: unknown[] + educations: unknown[] + certificates: unknown[] + } +} + +export interface FindymailGetCompanyParams extends FindymailBaseParams { + linkedin_url?: string + domain?: string + name?: string +} + +export interface FindymailGetCompanyResponse extends ToolResponse { + output: { + name: string | null + domain: string | null + company_size: string | null + industry: string | null + linkedin_url: string | null + description: string | null + } +} + +export interface FindymailFindEmployeesParams extends FindymailBaseParams { + website: string + job_titles: string[] + count?: number +} + +export interface FindymailEmployee { + name: string + linkedinUrl: string | null + companyWebsite: string | null + companyName: string | null + jobTitle: string | null +} + +export interface FindymailFindEmployeesResponse extends ToolResponse { + output: { + employees: FindymailEmployee[] + } +} + +export interface FindymailFindPhoneParams extends FindymailBaseParams { + linkedin_url: string +} + +export interface FindymailFindPhoneResponse extends ToolResponse { + output: { + phone: string | null + line_type: string | null + } +} + +export interface FindymailSearchTechnologiesParams extends FindymailBaseParams { + q: string +} + +export interface FindymailTechnology { + name: string + category: string | null + subcategory: string | null + last_detected_at?: string | null +} + +export interface FindymailSearchTechnologiesResponse extends ToolResponse { + output: { + technologies: FindymailTechnology[] + } +} + +export interface FindymailLookupTechnologiesParams extends FindymailBaseParams { + domain: string + technologies?: string[] +} + +export interface FindymailLookupTechnologiesResponse extends ToolResponse { + output: { + domain: string + technologies: FindymailTechnology[] + } +} + +export interface FindymailGetCreditsParams extends FindymailBaseParams {} + +export interface FindymailGetCreditsResponse extends ToolResponse { + output: { + credits: number + verifier_credits: number + } +} + +export type FindymailResponse = + | FindymailVerifyEmailResponse + | FindymailFindEmailFromNameResponse + | FindymailFindEmailsByDomainResponse + | FindymailFindEmailFromLinkedInResponse + | FindymailReverseEmailLookupResponse + | FindymailGetCompanyResponse + | FindymailFindEmployeesResponse + | FindymailFindPhoneResponse + | FindymailSearchTechnologiesResponse + | FindymailLookupTechnologiesResponse + | FindymailGetCreditsResponse diff --git a/apps/sim/tools/findymail/verify_email.ts b/apps/sim/tools/findymail/verify_email.ts new file mode 100644 index 0000000000..ec5d97ad5d --- /dev/null +++ b/apps/sim/tools/findymail/verify_email.ts @@ -0,0 +1,71 @@ +import type { + FindymailVerifyEmailParams, + FindymailVerifyEmailResponse, +} from '@/tools/findymail/types' +import type { ToolConfig } from '@/tools/types' + +export const verifyEmailTool: ToolConfig = + { + id: 'findymail_verify_email', + name: 'Findymail Verify Email', + description: 'Verifies the deliverability of an email address. Uses one verifier credit.', + version: '1.0.0', + + params: { + email: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Email address to verify (e.g., john@example.com)', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Findymail API Key', + }, + }, + + request: { + url: 'https://app.findymail.com/api/verify', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + body: (params) => ({ email: params.email }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + return { + success: false, + error: + (errorData as Record).message || + `Findymail API error: ${response.status} ${response.statusText}`, + output: { email: '', verified: false, provider: null }, + } + } + const data = await response.json() + return { + success: true, + output: { + email: data.email ?? '', + verified: data.verified ?? false, + provider: data.provider ?? null, + }, + } + }, + + outputs: { + email: { type: 'string', description: 'The verified email address' }, + verified: { type: 'boolean', description: 'Whether the email is verified as deliverable' }, + provider: { + type: 'string', + description: 'Email service provider (e.g., Google, Microsoft)', + optional: true, + }, + }, + } diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 2f8b48ff3c..dad88d1a94 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -653,6 +653,19 @@ import { fileReadTool, fileWriteTool, } from '@/tools/file' +import { + findymailFindEmailFromLinkedInTool, + findymailFindEmailFromNameTool, + findymailFindEmailsByDomainTool, + findymailFindEmployeesTool, + findymailFindPhoneTool, + findymailGetCompanyTool, + findymailGetCreditsTool, + findymailLookupTechnologiesTool, + findymailReverseEmailLookupTool, + findymailSearchTechnologiesTool, + findymailVerifyEmailTool, +} from '@/tools/findymail' import { firecrawlAgentTool, firecrawlCrawlTool, @@ -4823,6 +4836,17 @@ export const tools: Record = { fathom_get_transcript: fathomGetTranscriptTool, fathom_list_team_members: fathomListTeamMembersTool, fathom_list_teams: fathomListTeamsTool, + findymail_verify_email: findymailVerifyEmailTool, + findymail_find_email_from_name: findymailFindEmailFromNameTool, + findymail_find_emails_by_domain: findymailFindEmailsByDomainTool, + findymail_find_email_from_linkedin: findymailFindEmailFromLinkedInTool, + findymail_reverse_email_lookup: findymailReverseEmailLookupTool, + findymail_get_company: findymailGetCompanyTool, + findymail_find_employees: findymailFindEmployeesTool, + findymail_find_phone: findymailFindPhoneTool, + findymail_search_technologies: findymailSearchTechnologiesTool, + findymail_lookup_technologies: findymailLookupTechnologiesTool, + findymail_get_credits: findymailGetCreditsTool, stt_whisper: whisperSttTool, stt_whisper_v2: whisperSttV2Tool, stt_deepgram: deepgramSttTool,