feat(instantly): block, trigger (#4763)

* feat(instantly): block, trigger

* change bg color for icon

* address comments

* cleanup code

* search param missplaced
This commit is contained in:
Vikhyath Mondreti
2026-05-27 16:36:21 -07:00
committed by GitHub
parent b197bb049e
commit 28766ddaa9
52 changed files with 4739 additions and 0 deletions
+12
View File
@@ -415,6 +415,18 @@ export function MailIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function InstantlyIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 766.8 766.8' xmlns='http://www.w3.org/2000/svg'>
<circle cx='383.4' cy='383.4' r='383.4' fill='#0081ff' />
<path
d='M276.12 438.81h-101.8c-3.58 0-5.83-3.87-4.05-6.98l163.07-284.97h238.63c3.87 0 6.06 4.44 3.69 7.51L459.07 305.59c-2.36 3.07-.18 7.51 3.69 7.51h124.51c4.2 0 6.26 5.11 3.23 8.02L235.8 662.51c-3.37 3.24-8.88.06-7.76-4.48l52.61-213.45c.72-2.93-1.5-5.77-4.53-5.77z'
fill='#fff'
/>
</svg>
)
}
export function EmailBisonIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='-66 -2 88 64' fill='none' xmlns='http://www.w3.org/2000/svg'>
+2
View File
@@ -99,6 +99,7 @@ import {
ImageIcon,
IncidentioIcon,
InfisicalIcon,
InstantlyIcon,
IntercomIcon,
JinaAIIcon,
JiraIcon,
@@ -319,6 +320,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
imap: MailServerIcon,
incidentio: IncidentioIcon,
infisical: InfisicalIcon,
instantly: InstantlyIcon,
intercom: IntercomIcon,
intercom_v2: IntercomIcon,
jina: JinaAIIcon,
@@ -0,0 +1,522 @@
---
title: Instantly
description: Manage Instantly leads, campaigns, emails, and lead lists
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="instantly"
color="#FFFFFF"
/>
## Usage Instructions
Integrate Instantly API V2 into workflows. Create and list leads, manage lead interest status, delete leads in bulk, list and create campaigns, reply to emails, and manage lead lists.
## Tools
### `instantly_list_leads`
Retrieves Instantly V2 leads with search, campaign, list, and pagination filters.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `search` | string | No | Search by first name, last name, or email |
| `filter` | string | No | Instantly lead filter value, such as FILTER_VAL_CONTACTED or FILTER_VAL_ACTIVE |
| `campaign` | string | No | Campaign ID to filter leads |
| `list_id` | string | No | Lead list ID to filter leads |
| `in_campaign` | boolean | No | Whether the lead is in a campaign |
| `in_list` | boolean | No | Whether the lead is in a list |
| `ids` | array | No | Lead IDs to include |
| `items` | string | No | No description |
| `excluded_ids` | array | No | Lead IDs to exclude |
| `items` | string | No | No description |
| `contacts` | array | No | Lead email addresses to include |
| `items` | string | No | No description |
| `limit` | number | No | Number of leads to return, from 1 to 100 |
| `starting_after` | string | No | Forward pagination cursor from next_starting_after |
| `distinct_contacts` | boolean | No | Whether to return distinct contacts |
| `enrichment_status` | number | No | Enrichment status filter |
| `esg_code` | string | No | Email security gateway code filter |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `leads` | array | List of leads |
| `lead` | json | Lead details |
| `campaigns` | array | List of campaigns |
| `campaign` | json | Campaign details |
| `emails` | array | List of emails |
| `email` | json | Email details |
| `lead_lists` | array | List of lead lists |
| `lead_list` | json | Lead list details |
| `count` | number | Returned or affected record count |
| `next_starting_after` | string | Cursor for the next page |
| `id` | string | Record ID |
| `name` | string | Record name |
| `email_address` | string | Lead email address |
| `first_name` | string | Lead first name |
| `last_name` | string | Lead last name |
| `status` | number | Lead or campaign status |
| `subject` | string | Email subject |
| `thread_id` | string | Email thread ID |
| `message` | string | Operation message |
### `instantly_get_lead`
Retrieves an Instantly V2 lead by ID.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `leadId` | string | Yes | Lead ID |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `leads` | array | List of leads |
| `lead` | json | Lead details |
| `campaigns` | array | List of campaigns |
| `campaign` | json | Campaign details |
| `emails` | array | List of emails |
| `email` | json | Email details |
| `lead_lists` | array | List of lead lists |
| `lead_list` | json | Lead list details |
| `count` | number | Returned or affected record count |
| `next_starting_after` | string | Cursor for the next page |
| `id` | string | Record ID |
| `name` | string | Record name |
| `email_address` | string | Lead email address |
| `first_name` | string | Lead first name |
| `last_name` | string | Lead last name |
| `status` | number | Lead or campaign status |
| `subject` | string | Email subject |
| `thread_id` | string | Email thread ID |
| `message` | string | Operation message |
### `instantly_create_lead`
Creates an Instantly V2 lead in a campaign or lead list.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `campaign` | string | No | Campaign ID associated with the lead |
| `list_id` | string | No | Lead list ID associated with the lead |
| `email` | string | No | Lead email address. Required when adding to a campaign. |
| `first_name` | string | No | Lead first name |
| `last_name` | string | No | Lead last name |
| `company_name` | string | No | Lead company name |
| `job_title` | string | No | Lead job title |
| `phone` | string | No | Lead phone number |
| `website` | string | No | Lead website |
| `personalization` | string | No | Lead personalization text |
| `lt_interest_status` | number | No | Lead interest status value |
| `pl_value_lead` | string | No | Potential value of the lead |
| `assigned_to` | string | No | Organization user ID assigned to the lead |
| `skip_if_in_workspace` | boolean | No | Skip if the lead already exists in the workspace |
| `skip_if_in_campaign` | boolean | No | Skip if the lead already exists in the campaign |
| `skip_if_in_list` | boolean | No | Skip if the lead already exists in the list |
| `blocklist_id` | string | No | Blocklist ID to check for the lead |
| `verify_leads_on_import` | boolean | No | Whether to verify leads on import |
| `custom_variables` | json | No | Custom variable object with string, number, boolean, or null values |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `leads` | array | List of leads |
| `lead` | json | Lead details |
| `campaigns` | array | List of campaigns |
| `campaign` | json | Campaign details |
| `emails` | array | List of emails |
| `email` | json | Email details |
| `lead_lists` | array | List of lead lists |
| `lead_list` | json | Lead list details |
| `count` | number | Returned or affected record count |
| `next_starting_after` | string | Cursor for the next page |
| `id` | string | Record ID |
| `name` | string | Record name |
| `email_address` | string | Lead email address |
| `first_name` | string | Lead first name |
| `last_name` | string | Lead last name |
| `status` | number | Lead or campaign status |
| `subject` | string | Email subject |
| `thread_id` | string | Email thread ID |
| `message` | string | Operation message |
### `instantly_delete_leads`
Deletes Instantly V2 leads in bulk from a campaign or lead list.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `campaign_id` | string | No | Campaign ID to delete leads from. Required if list_id is not provided. |
| `list_id` | string | No | Lead list ID to delete leads from. Required if campaign_id is not provided. |
| `status` | number | No | Optional lead status filter |
| `ids` | array | No | Specific lead IDs to delete |
| `items` | string | No | No description |
| `limit` | number | No | Maximum number of matching leads to delete, up to 10000 |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `count` | number | Number of leads deleted |
### `instantly_update_lead_interest_status`
Submits an Instantly V2 background job to update a lead interest status.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `lead_email` | string | Yes | Lead email address |
| `interest_value` | number | Yes | Interest status value. Use null in JSON/tool input to reset to Lead. |
| `campaign_id` | string | No | Campaign ID for the lead |
| `list_id` | string | No | Lead list ID for the lead |
| `ai_interest_value` | number | No | AI interest value to set for the lead |
| `disable_auto_interest` | boolean | No | Whether to disable auto interest |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `message` | string | Background job submission message |
### `instantly_list_campaigns`
Retrieves Instantly V2 campaigns with search, status, tag, and pagination filters.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `limit` | number | No | Number of campaigns to return, from 1 to 100 |
| `starting_after` | string | No | Pagination cursor from next_starting_after |
| `search` | string | No | Search by campaign name |
| `tag_ids` | string | No | Comma-separated campaign tag IDs |
| `ai_sales_agent_id` | string | No | AI Sales Agent ID filter |
| `status` | number | No | Campaign status enum value |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `leads` | array | List of leads |
| `lead` | json | Lead details |
| `campaigns` | array | List of campaigns |
| `campaign` | json | Campaign details |
| `emails` | array | List of emails |
| `email` | json | Email details |
| `lead_lists` | array | List of lead lists |
| `lead_list` | json | Lead list details |
| `count` | number | Returned or affected record count |
| `next_starting_after` | string | Cursor for the next page |
| `id` | string | Record ID |
| `name` | string | Record name |
| `email_address` | string | Lead email address |
| `first_name` | string | Lead first name |
| `last_name` | string | Lead last name |
| `status` | number | Lead or campaign status |
| `subject` | string | Email subject |
| `thread_id` | string | Email thread ID |
| `message` | string | Operation message |
### `instantly_create_campaign`
Creates an Instantly V2 campaign using the documented campaign schedule schema.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `name` | string | Yes | Campaign name |
| `campaign_schedule` | json | Yes | Campaign schedule object with schedules array |
| `sequences` | array | No | Campaign sequence definitions |
| `items` | object | No | No description |
| `email_list` | array | No | Sending email accounts |
| `items` | string | No | No description |
| `daily_limit` | number | No | Daily sending limit |
| `daily_max_leads` | number | No | Daily maximum new leads to contact |
| `open_tracking` | boolean | No | Whether to track opens |
| `stop_on_reply` | boolean | No | Whether to stop the campaign on reply |
| `link_tracking` | boolean | No | Whether to track links |
| `text_only` | boolean | No | Whether the campaign is text only |
| `email_gap` | number | No | Gap between emails in minutes |
| `pl_value` | number | No | Value of every positive lead |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `leads` | array | List of leads |
| `lead` | json | Lead details |
| `campaigns` | array | List of campaigns |
| `campaign` | json | Campaign details |
| `emails` | array | List of emails |
| `email` | json | Email details |
| `lead_lists` | array | List of lead lists |
| `lead_list` | json | Lead list details |
| `count` | number | Returned or affected record count |
| `next_starting_after` | string | Cursor for the next page |
| `id` | string | Record ID |
| `name` | string | Record name |
| `email_address` | string | Lead email address |
| `first_name` | string | Lead first name |
| `last_name` | string | Lead last name |
| `status` | number | Lead or campaign status |
| `subject` | string | Email subject |
| `thread_id` | string | Email thread ID |
| `message` | string | Operation message |
### `instantly_patch_campaign`
Updates documented Instantly V2 campaign fields.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `campaignId` | string | Yes | Campaign ID |
| `name` | string | No | Campaign name |
| `campaign_schedule` | json | No | Campaign schedule object with schedules array |
| `sequences` | array | No | Campaign sequence definitions |
| `items` | object | No | No description |
| `email_list` | array | No | Sending email accounts |
| `items` | string | No | No description |
| `daily_limit` | number | No | Daily sending limit |
| `daily_max_leads` | number | No | Daily maximum new leads to contact |
| `open_tracking` | boolean | No | Whether to track opens |
| `stop_on_reply` | boolean | No | Whether to stop the campaign on reply |
| `link_tracking` | boolean | No | Whether to track links |
| `text_only` | boolean | No | Whether the campaign is text only |
| `email_gap` | number | No | Gap between emails in minutes |
| `pl_value` | number | No | Value of every positive lead |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `leads` | array | List of leads |
| `lead` | json | Lead details |
| `campaigns` | array | List of campaigns |
| `campaign` | json | Campaign details |
| `emails` | array | List of emails |
| `email` | json | Email details |
| `lead_lists` | array | List of lead lists |
| `lead_list` | json | Lead list details |
| `count` | number | Returned or affected record count |
| `next_starting_after` | string | Cursor for the next page |
| `id` | string | Record ID |
| `name` | string | Record name |
| `email_address` | string | Lead email address |
| `first_name` | string | Lead first name |
| `last_name` | string | Lead last name |
| `status` | number | Lead or campaign status |
| `subject` | string | Email subject |
| `thread_id` | string | Email thread ID |
| `message` | string | Operation message |
### `instantly_activate_campaign`
Activates, starts, or resumes an Instantly V2 campaign.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `campaignId` | string | Yes | Campaign ID |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `leads` | array | List of leads |
| `lead` | json | Lead details |
| `campaigns` | array | List of campaigns |
| `campaign` | json | Campaign details |
| `emails` | array | List of emails |
| `email` | json | Email details |
| `lead_lists` | array | List of lead lists |
| `lead_list` | json | Lead list details |
| `count` | number | Returned or affected record count |
| `next_starting_after` | string | Cursor for the next page |
| `id` | string | Record ID |
| `name` | string | Record name |
| `email_address` | string | Lead email address |
| `first_name` | string | Lead first name |
| `last_name` | string | Lead last name |
| `status` | number | Lead or campaign status |
| `subject` | string | Email subject |
| `thread_id` | string | Email thread ID |
| `message` | string | Operation message |
### `instantly_list_emails`
Retrieves Instantly V2 Unibox emails with search and pagination filters.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `limit` | number | No | Number of emails to return, from 1 to 100 |
| `starting_after` | string | No | Pagination cursor from next_starting_after |
| `search` | string | No | Search query, email address, or thread:&lt;thread-id&gt; |
| `campaign_id` | string | No | Campaign ID filter |
| `list_id` | string | No | Lead list ID filter |
| `i_status` | number | No | Email interest status filter |
| `eaccount` | string | No | Sending email account filter |
| `lead` | string | No | Lead email address filter |
| `lead_id` | string | No | Lead ID filter |
| `is_unread` | number | No | Unread status filter |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `leads` | array | List of leads |
| `lead` | json | Lead details |
| `campaigns` | array | List of campaigns |
| `campaign` | json | Campaign details |
| `emails` | array | List of emails |
| `email` | json | Email details |
| `lead_lists` | array | List of lead lists |
| `lead_list` | json | Lead list details |
| `count` | number | Returned or affected record count |
| `next_starting_after` | string | Cursor for the next page |
| `id` | string | Record ID |
| `name` | string | Record name |
| `email_address` | string | Lead email address |
| `first_name` | string | Lead first name |
| `last_name` | string | Lead last name |
| `status` | number | Lead or campaign status |
| `subject` | string | Email subject |
| `thread_id` | string | Email thread ID |
| `message` | string | Operation message |
### `instantly_reply_to_email`
Sends an Instantly V2 reply to an existing Unibox email.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `eaccount` | string | Yes | Connected email account used to send the reply |
| `reply_to_uuid` | string | Yes | Email ID to reply to |
| `subject` | string | Yes | Reply subject |
| `body` | json | Yes | Reply body object with text and/or html |
| `cc_address_email_list` | string | No | Comma-separated CC email addresses |
| `bcc_address_email_list` | string | No | Comma-separated BCC email addresses |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `leads` | array | List of leads |
| `lead` | json | Lead details |
| `campaigns` | array | List of campaigns |
| `campaign` | json | Campaign details |
| `emails` | array | List of emails |
| `email` | json | Email details |
| `lead_lists` | array | List of lead lists |
| `lead_list` | json | Lead list details |
| `count` | number | Returned or affected record count |
| `next_starting_after` | string | Cursor for the next page |
| `id` | string | Record ID |
| `name` | string | Record name |
| `email_address` | string | Lead email address |
| `first_name` | string | Lead first name |
| `last_name` | string | Lead last name |
| `status` | number | Lead or campaign status |
| `subject` | string | Email subject |
| `thread_id` | string | Email thread ID |
| `message` | string | Operation message |
### `instantly_list_lead_lists`
Retrieves Instantly V2 lead lists with search and pagination filters.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `limit` | number | No | Number of lead lists to return, from 1 to 100 |
| `starting_after` | string | No | Starting-after timestamp cursor |
| `has_enrichment_task` | boolean | No | Filter by enrichment task setting |
| `search` | string | No | Search query to filter lead lists by name |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `leads` | array | List of leads |
| `lead` | json | Lead details |
| `campaigns` | array | List of campaigns |
| `campaign` | json | Campaign details |
| `emails` | array | List of emails |
| `email` | json | Email details |
| `lead_lists` | array | List of lead lists |
| `lead_list` | json | Lead list details |
| `count` | number | Returned or affected record count |
| `next_starting_after` | string | Cursor for the next page |
| `id` | string | Record ID |
| `name` | string | Record name |
| `email_address` | string | Lead email address |
| `first_name` | string | Lead first name |
| `last_name` | string | Lead last name |
| `status` | number | Lead or campaign status |
| `subject` | string | Email subject |
| `thread_id` | string | Email thread ID |
| `message` | string | Operation message |
### `instantly_create_lead_list`
Creates an Instantly V2 lead list.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `name` | string | Yes | Lead list name |
| `has_enrichment_task` | boolean | No | Whether this list runs enrichment for every added lead |
| `owned_by` | string | No | User ID of the lead list owner |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `leads` | array | List of leads |
| `lead` | json | Lead details |
| `campaigns` | array | List of campaigns |
| `campaign` | json | Campaign details |
| `emails` | array | List of emails |
| `email` | json | Email details |
| `lead_lists` | array | List of lead lists |
| `lead_list` | json | Lead list details |
| `count` | number | Returned or affected record count |
| `next_starting_after` | string | Cursor for the next page |
| `id` | string | Record ID |
| `name` | string | Record name |
| `email_address` | string | Lead email address |
| `first_name` | string | Lead first name |
| `last_name` | string | Lead last name |
| `status` | number | Lead or campaign status |
| `subject` | string | Email subject |
| `thread_id` | string | Email thread ID |
| `message` | string | Operation message |
@@ -95,6 +95,7 @@
"imap",
"incidentio",
"infisical",
"instantly",
"intercom",
"jina",
"jira",
@@ -98,6 +98,7 @@ import {
ImageIcon,
IncidentioIcon,
InfisicalIcon,
InstantlyIcon,
IntercomIcon,
JinaAIIcon,
JiraIcon,
@@ -305,6 +306,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
imap: MailServerIcon,
incidentio: IncidentioIcon,
infisical: InfisicalIcon,
instantly: InstantlyIcon,
intercom_v2: IntercomIcon,
jina: JinaAIIcon,
jira: JiraIcon,
@@ -7028,6 +7028,178 @@
"integrationTypes": ["security"],
"tags": ["secrets-management"]
},
{
"type": "instantly",
"slug": "instantly",
"name": "Instantly",
"description": "Manage Instantly leads, campaigns, emails, and lead lists",
"longDescription": "Integrate Instantly API V2 into workflows. Create and list leads, manage lead interest status, delete leads in bulk, list and create campaigns, reply to emails, and manage lead lists.",
"bgColor": "#FFFFFF",
"iconName": "InstantlyIcon",
"docsUrl": "https://docs.sim.ai/tools/instantly",
"operations": [
{
"name": "List Leads",
"description": "Retrieves Instantly V2 leads with search, campaign, list, and pagination filters."
},
{
"name": "Get Lead",
"description": "Retrieves an Instantly V2 lead by ID."
},
{
"name": "Create Lead",
"description": "Creates an Instantly V2 lead in a campaign or lead list."
},
{
"name": "Delete Leads",
"description": "Deletes Instantly V2 leads in bulk from a campaign or lead list."
},
{
"name": "Update Lead Interest Status",
"description": "Submits an Instantly V2 background job to update a lead interest status."
},
{
"name": "List Campaigns",
"description": "Retrieves Instantly V2 campaigns with search, status, tag, and pagination filters."
},
{
"name": "Create Campaign",
"description": "Creates an Instantly V2 campaign using the documented campaign schedule schema."
},
{
"name": "Patch Campaign",
"description": "Updates documented Instantly V2 campaign fields."
},
{
"name": "Activate Campaign",
"description": "Activates, starts, or resumes an Instantly V2 campaign."
},
{
"name": "List Emails",
"description": "Retrieves Instantly V2 Unibox emails with search and pagination filters."
},
{
"name": "Reply To Email",
"description": "Sends an Instantly V2 reply to an existing Unibox email."
},
{
"name": "List Lead Lists",
"description": "Retrieves Instantly V2 lead lists with search and pagination filters."
},
{
"name": "Create Lead List",
"description": "Creates an Instantly V2 lead list."
}
],
"operationCount": 13,
"triggers": [
{
"id": "instantly_webhook",
"name": "Instantly Webhook",
"description": "Trigger workflow on any Instantly webhook event"
},
{
"id": "instantly_email_sent",
"name": "Instantly Email Sent",
"description": "Trigger when Instantly sends an email"
},
{
"id": "instantly_email_opened",
"name": "Instantly Email Opened",
"description": "Trigger when a lead opens an Instantly email"
},
{
"id": "instantly_reply_received",
"name": "Instantly Reply Received",
"description": "Trigger when a lead replies to an Instantly email"
},
{
"id": "instantly_auto_reply_received",
"name": "Instantly Auto Reply Received",
"description": "Trigger when Instantly receives an auto-reply from a lead"
},
{
"id": "instantly_link_clicked",
"name": "Instantly Link Clicked",
"description": "Trigger when a lead clicks a tracked Instantly link"
},
{
"id": "instantly_email_bounced",
"name": "Instantly Email Bounced",
"description": "Trigger when an Instantly email bounces"
},
{
"id": "instantly_lead_unsubscribed",
"name": "Instantly Lead Unsubscribed",
"description": "Trigger when an Instantly lead unsubscribes"
},
{
"id": "instantly_account_error",
"name": "Instantly Account Error",
"description": "Trigger when Instantly reports an account-level error"
},
{
"id": "instantly_campaign_completed",
"name": "Instantly Campaign Completed",
"description": "Trigger when an Instantly campaign completes"
},
{
"id": "instantly_lead_neutral",
"name": "Instantly Lead Neutral",
"description": "Trigger when an Instantly lead is marked neutral"
},
{
"id": "instantly_lead_interested",
"name": "Instantly Lead Interested",
"description": "Trigger when an Instantly lead is marked interested"
},
{
"id": "instantly_lead_not_interested",
"name": "Instantly Lead Not Interested",
"description": "Trigger when an Instantly lead is marked not interested"
},
{
"id": "instantly_lead_meeting_booked",
"name": "Instantly Lead Meeting Booked",
"description": "Trigger when an Instantly lead books a meeting"
},
{
"id": "instantly_lead_meeting_completed",
"name": "Instantly Lead Meeting Completed",
"description": "Trigger when an Instantly lead completes a meeting"
},
{
"id": "instantly_lead_closed",
"name": "Instantly Lead Closed",
"description": "Trigger when an Instantly lead is marked closed"
},
{
"id": "instantly_lead_out_of_office",
"name": "Instantly Lead Out Of Office",
"description": "Trigger when an Instantly lead is out of office"
},
{
"id": "instantly_lead_wrong_person",
"name": "Instantly Lead Wrong Person",
"description": "Trigger when an Instantly lead is marked wrong person"
},
{
"id": "instantly_lead_no_show",
"name": "Instantly Lead No Show",
"description": "Trigger when an Instantly lead is marked no show"
},
{
"id": "instantly_supersearch_enrichment_completed",
"name": "Instantly Supersearch Enrichment Completed",
"description": "Trigger when Instantly completes a Supersearch enrichment"
}
],
"triggerCount": 20,
"authType": "api-key",
"category": "tools",
"integrationTypes": ["email", "developer-tools", "sales"],
"tags": ["sales-engagement", "email-marketing", "automation"]
},
{
"type": "intercom_v2",
"slug": "intercom",
File diff suppressed because it is too large Load Diff
+2
View File
@@ -103,6 +103,7 @@ import { ImapBlock } from '@/blocks/blocks/imap'
import { IncidentioBlock } from '@/blocks/blocks/incidentio'
import { InfisicalBlock } from '@/blocks/blocks/infisical'
import { InputTriggerBlock } from '@/blocks/blocks/input_trigger'
import { InstantlyBlock } from '@/blocks/blocks/instantly'
import { IntercomBlock, IntercomV2Block } from '@/blocks/blocks/intercom'
import { JinaBlock } from '@/blocks/blocks/jina'
import { JiraBlock } from '@/blocks/blocks/jira'
@@ -364,6 +365,7 @@ export const registry: Record<string, BlockConfig> = {
incidentio: IncidentioBlock,
infisical: InfisicalBlock,
input_trigger: InputTriggerBlock,
instantly: InstantlyBlock,
intercom: IntercomBlock,
intercom_v2: IntercomV2Block,
jina: JinaBlock,
+12
View File
@@ -430,6 +430,18 @@ export function MailIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function InstantlyIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 766.8 766.8' xmlns='http://www.w3.org/2000/svg'>
<circle cx='383.4' cy='383.4' r='383.4' fill='#0081ff' />
<path
d='M276.12 438.81h-101.8c-3.58 0-5.83-3.87-4.05-6.98l163.07-284.97h238.63c3.87 0 6.06 4.44 3.69 7.51L459.07 305.59c-2.36 3.07-.18 7.51 3.69 7.51h124.51c4.2 0 6.26 5.11 3.23 8.02L235.8 662.51c-3.37 3.24-8.88.06-7.76-4.48l52.61-213.45c.72-2.93-1.5-5.77-4.53-5.77z'
fill='#fff'
/>
</svg>
)
}
export function EmailBisonIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='-66 -2 88 64' fill='none' xmlns='http://www.w3.org/2000/svg'>
@@ -0,0 +1,269 @@
import { createLogger } from '@sim/logger'
import { toError } from '@sim/utils/errors'
import { generateShortId } from '@sim/utils/id'
import { NextResponse } from 'next/server'
import { getNotificationUrl, getProviderConfig } from '@/lib/webhooks/provider-subscription-utils'
import type {
AuthContext,
DeleteSubscriptionContext,
EventMatchContext,
FormatInputContext,
FormatInputResult,
SubscriptionContext,
SubscriptionResult,
WebhookProviderHandler,
} from '@/lib/webhooks/providers/types'
import { verifyTokenAuth } from '@/lib/webhooks/providers/utils'
import { instantlyUrl } from '@/tools/instantly/utils'
const logger = createLogger('WebhookProvider:Instantly')
const SIM_WEBHOOK_TOKEN_HEADER = 'x-sim-webhook-token'
export const instantlyHandler: WebhookProviderHandler = {
verifyAuth({ request, requestId, providerConfig }: AuthContext): NextResponse | null {
const secretToken = providerConfig.secretToken as string | undefined
if (!secretToken) {
logger.warn(`[${requestId}] Instantly webhook secret token is missing`)
return new NextResponse('Unauthorized', { status: 401 })
}
if (!verifyTokenAuth(request, secretToken, SIM_WEBHOOK_TOKEN_HEADER)) {
logger.warn(`[${requestId}] Unauthorized Instantly webhook request`)
return new NextResponse('Unauthorized', { status: 401 })
}
return null
},
async matchEvent({ body, providerConfig, requestId }: EventMatchContext): Promise<boolean> {
const triggerId = providerConfig.triggerId as string | undefined
if (!triggerId) return true
if (!isRecord(body)) {
logger.warn(`[${requestId}] Instantly webhook payload was not an object`)
return false
}
const { isInstantlyEventMatch } = await import('@/triggers/instantly/utils')
if (!isInstantlyEventMatch(triggerId, body)) {
logger.info(`[${requestId}] Instantly event did not match trigger`, {
triggerId,
eventType: body.event_type,
})
return false
}
return true
},
async formatInput({ body }: FormatInputContext): Promise<FormatInputResult> {
const payload = isRecord(body) ? body : {}
return {
input: {
timestamp: toStringOrNull(payload.timestamp),
eventType: toStringOrNull(payload.event_type),
workspace: toStringOrNull(payload.workspace),
campaignId: toStringOrNull(payload.campaign_id),
campaignName: toStringOrNull(payload.campaign_name),
leadEmail: toStringOrNull(payload.lead_email),
emailAccount: toStringOrNull(payload.email_account),
uniboxUrl: toStringOrNull(payload.unibox_url),
step: toNumberOrNull(payload.step),
variant: toNumberOrNull(payload.variant),
isFirst: toBooleanOrNull(payload.is_first),
emailId: toStringOrNull(payload.email_id),
emailSubject: toStringOrNull(payload.email_subject),
emailText: toStringOrNull(payload.email_text),
emailHtml: toStringOrNull(payload.email_html),
replyTextSnippet: toStringOrNull(payload.reply_text_snippet),
replySubject: toStringOrNull(payload.reply_subject),
replyText: toStringOrNull(payload.reply_text),
replyHtml: toStringOrNull(payload.reply_html),
payload,
},
}
},
async createSubscription(ctx: SubscriptionContext): Promise<SubscriptionResult | undefined> {
const { webhook, requestId } = ctx
const providerConfig = getProviderConfig(webhook)
const apiKey = providerConfig.triggerApiKey as string | undefined
const triggerId = providerConfig.triggerId as string | undefined
const campaignId = optionalId(providerConfig.triggerCampaignId)
if (!apiKey?.trim()) {
throw new Error('Instantly API Key is required.')
}
if (!triggerId) {
throw new Error('Instantly trigger ID is required.')
}
const { getInstantlySubscriptionEventTypeForTrigger } = await import(
'@/triggers/instantly/utils'
)
const eventType = getInstantlySubscriptionEventTypeForTrigger(triggerId)
if (!eventType) {
throw new Error(`Unknown Instantly trigger type: ${triggerId}`)
}
const secretToken =
typeof providerConfig.secretToken === 'string' && providerConfig.secretToken.length > 0
? providerConfig.secretToken
: generateShortId(32)
const requestBody: Record<string, unknown> = {
name: `Sim - ${triggerId.replace(/^instantly_/, '').replace(/_/g, ' ')}`,
target_hook_url: getNotificationUrl(webhook),
event_type: eventType,
headers: {
'X-Sim-Webhook-Token': secretToken,
},
}
if (campaignId) {
requestBody.campaign = campaignId
}
logger.info(`[${requestId}] Creating Instantly webhook`, {
triggerId,
eventType,
hasCampaignId: Boolean(campaignId),
webhookId: webhook.id,
})
const response = await fetch(instantlyUrl('/api/v2/webhooks'), {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey.trim()}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
})
const responseBody = await parseJsonResponse(response)
if (!response.ok) {
const message = extractInstantlyError(responseBody)
logger.error(`[${requestId}] Failed to create Instantly webhook`, {
status: response.status,
message,
response: responseBody,
})
if (response.status === 401 || response.status === 403) {
throw new Error('Invalid Instantly API Key or missing webhook permissions.')
}
if (response.status === 402) {
throw new Error('Instantly webhook creation requires an active paid plan.')
}
throw new Error(
message ? `Instantly error: ${message}` : 'Failed to create Instantly webhook'
)
}
const externalId = responseBody?.id
if (typeof externalId !== 'string' || externalId.length === 0) {
throw new Error('Instantly webhook was created but the API response did not include an ID.')
}
logger.info(`[${requestId}] Successfully created Instantly webhook`, {
externalId,
webhookId: webhook.id,
})
return { providerConfigUpdates: { externalId, secretToken } }
},
async deleteSubscription(ctx: DeleteSubscriptionContext): Promise<void> {
const { webhook, requestId } = ctx
try {
const providerConfig = getProviderConfig(webhook)
const apiKey = providerConfig.triggerApiKey as string | undefined
const externalId = providerConfig.externalId as string | undefined
if (!apiKey?.trim() || !externalId?.trim()) {
logger.warn(`[${requestId}] Missing Instantly webhook cleanup configuration`, {
webhookId: webhook.id,
hasApiKey: Boolean(apiKey),
hasExternalId: Boolean(externalId),
})
if (ctx.strict) throw new Error('Missing Instantly webhook cleanup configuration')
return
}
const response = await fetch(
instantlyUrl(`/api/v2/webhooks/${encodeURIComponent(externalId.trim())}`),
{
method: 'DELETE',
headers: {
Authorization: `Bearer ${apiKey.trim()}`,
},
}
)
if (!response.ok && response.status !== 404) {
const responseBody = await parseJsonResponse(response)
logger.warn(`[${requestId}] Failed to delete Instantly webhook`, {
status: response.status,
response: responseBody,
})
if (ctx.strict) throw new Error(`Failed to delete Instantly webhook: ${response.status}`)
return
}
await response.body?.cancel()
logger.info(`[${requestId}] Successfully deleted Instantly webhook`, {
externalId,
webhookId: webhook.id,
})
} catch (error) {
logger.warn(`[${requestId}] Error deleting Instantly webhook`, {
message: toError(error).message,
})
if (ctx.strict) throw error
}
},
}
async function parseJsonResponse(response: Response): Promise<Record<string, unknown> | null> {
try {
const body: unknown = await response.json()
return isRecord(body) ? body : null
} catch {
return null
}
}
function extractInstantlyError(body: Record<string, unknown> | null): string | null {
if (!body) return null
if (typeof body.message === 'string') return body.message
if (typeof body.error === 'string') return body.error
return null
}
function toStringOrNull(value: unknown): string | null {
return typeof value === 'string' ? value : null
}
function toNumberOrNull(value: unknown): number | null {
return typeof value === 'number' && Number.isFinite(value) ? value : null
}
function toBooleanOrNull(value: unknown): boolean | null {
return typeof value === 'boolean' ? value : null
}
function optionalId(value: unknown): string | undefined {
if (typeof value !== 'string') return undefined
const trimmed = value.trim()
if (trimmed === '' || trimmed === '-') return undefined
return trimmed
}
function isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
}
@@ -19,6 +19,7 @@ import { googleFormsHandler } from '@/lib/webhooks/providers/google-forms'
import { grainHandler } from '@/lib/webhooks/providers/grain'
import { greenhouseHandler } from '@/lib/webhooks/providers/greenhouse'
import { imapHandler } from '@/lib/webhooks/providers/imap'
import { instantlyHandler } from '@/lib/webhooks/providers/instantly'
import { intercomHandler } from '@/lib/webhooks/providers/intercom'
import { jiraHandler } from '@/lib/webhooks/providers/jira'
import { jsmHandler } from '@/lib/webhooks/providers/jsm'
@@ -69,6 +70,7 @@ const PROVIDER_HANDLERS: Record<string, WebhookProviderHandler> = {
greenhouse: greenhouseHandler,
imap: imapHandler,
intercom: intercomHandler,
instantly: instantlyHandler,
jira: jiraHandler,
jsm: jsmHandler,
lemlist: lemlistHandler,
@@ -0,0 +1,52 @@
import type {
InstantlyActivateCampaignParams,
InstantlyCampaignResponse,
} from '@/tools/instantly/types'
import {
campaignOutputs,
instantlyBaseParamFields,
instantlyHeaders,
instantlyUrl,
mapCampaign,
parseInstantlyResponse,
} from '@/tools/instantly/utils'
import type { ToolConfig } from '@/tools/types'
export const activateCampaignTool: ToolConfig<
InstantlyActivateCampaignParams,
InstantlyCampaignResponse
> = {
id: 'instantly_activate_campaign',
name: 'Instantly Activate Campaign',
description: 'Activates, starts, or resumes an Instantly V2 campaign.',
version: '1.0.0',
params: {
...instantlyBaseParamFields,
campaignId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Campaign ID',
},
},
request: {
url: (params) => instantlyUrl(`/api/v2/campaigns/${params.campaignId.trim()}/activate`),
method: 'POST',
headers: instantlyHeaders,
},
transformResponse: async (response) => {
const data = await parseInstantlyResponse(response)
const campaign = mapCampaign(data)
return {
success: true,
output: {
campaign,
id: campaign.id,
name: campaign.name,
status: campaign.status,
},
}
},
outputs: campaignOutputs,
}
+136
View File
@@ -0,0 +1,136 @@
import type {
InstantlyCampaignResponse,
InstantlyCreateCampaignParams,
} from '@/tools/instantly/types'
import {
campaignOutputs,
compactBody,
instantlyBaseParamFields,
instantlyHeaders,
instantlyUrl,
mapCampaign,
parseInstantlyResponse,
} from '@/tools/instantly/utils'
import type { ToolConfig } from '@/tools/types'
export const createCampaignTool: ToolConfig<
InstantlyCreateCampaignParams,
InstantlyCampaignResponse
> = {
id: 'instantly_create_campaign',
name: 'Instantly Create Campaign',
description: 'Creates an Instantly V2 campaign using the documented campaign schedule schema.',
version: '1.0.0',
params: {
...instantlyBaseParamFields,
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Campaign name',
},
campaign_schedule: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'Campaign schedule object with schedules array',
},
sequences: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Campaign sequence definitions',
items: { type: 'object', description: 'Sequence object' },
},
email_list: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Sending email accounts',
items: { type: 'string', description: 'Email address' },
},
daily_limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Daily sending limit',
},
daily_max_leads: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Daily maximum new leads to contact',
},
open_tracking: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to track opens',
},
stop_on_reply: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to stop the campaign on reply',
},
link_tracking: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to track links',
},
text_only: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether the campaign is text only',
},
email_gap: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Gap between emails in minutes',
},
pl_value: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Value of every positive lead',
},
},
request: {
url: () => instantlyUrl('/api/v2/campaigns'),
method: 'POST',
headers: instantlyHeaders,
body: (params) =>
compactBody({
name: params.name,
campaign_schedule: params.campaign_schedule,
sequences: params.sequences,
pl_value: params.pl_value,
email_gap: params.email_gap,
text_only: params.text_only,
email_list: params.email_list,
daily_limit: params.daily_limit,
stop_on_reply: params.stop_on_reply,
link_tracking: params.link_tracking,
open_tracking: params.open_tracking,
daily_max_leads: params.daily_max_leads,
}),
},
transformResponse: async (response) => {
const data = await parseInstantlyResponse(response)
const campaign = mapCampaign(data)
return {
success: true,
output: {
campaign,
id: campaign.id,
name: campaign.name,
status: campaign.status,
},
}
},
outputs: campaignOutputs,
}
+187
View File
@@ -0,0 +1,187 @@
import type { InstantlyCreateLeadParams, InstantlyLeadResponse } from '@/tools/instantly/types'
import {
compactBody,
instantlyBaseParamFields,
instantlyHeaders,
instantlyUrl,
leadOutputs,
mapLead,
parseInstantlyResponse,
} from '@/tools/instantly/utils'
import type { ToolConfig } from '@/tools/types'
export const createLeadTool: ToolConfig<InstantlyCreateLeadParams, InstantlyLeadResponse> = {
id: 'instantly_create_lead',
name: 'Instantly Create Lead',
description: 'Creates an Instantly V2 lead in a campaign or lead list.',
version: '1.0.0',
params: {
...instantlyBaseParamFields,
campaign: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Campaign ID associated with the lead',
},
list_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Lead list ID associated with the lead',
},
email: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Lead email address. Required when adding to a campaign.',
},
first_name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Lead first name',
},
last_name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Lead last name',
},
company_name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Lead company name',
},
job_title: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Lead job title',
},
phone: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Lead phone number',
},
website: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Lead website',
},
personalization: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Lead personalization text',
},
lt_interest_status: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Lead interest status value',
},
pl_value_lead: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Potential value of the lead',
},
assigned_to: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Organization user ID assigned to the lead',
},
skip_if_in_workspace: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Skip if the lead already exists in the workspace',
},
skip_if_in_campaign: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Skip if the lead already exists in the campaign',
},
skip_if_in_list: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Skip if the lead already exists in the list',
},
blocklist_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Blocklist ID to check for the lead',
},
verify_leads_for_lead_finder: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to verify leads imported from Lead Finder',
},
verify_leads_on_import: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to verify leads on import',
},
custom_variables: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'Custom variable object with string, number, boolean, or null values',
},
},
request: {
url: () => instantlyUrl('/api/v2/leads'),
method: 'POST',
headers: instantlyHeaders,
body: (params) =>
compactBody({
campaign: params.campaign,
list_id: params.list_id,
email: params.email,
first_name: params.first_name,
last_name: params.last_name,
company_name: params.company_name,
job_title: params.job_title,
phone: params.phone,
website: params.website,
personalization: params.personalization,
lt_interest_status: params.lt_interest_status,
pl_value_lead: params.pl_value_lead,
assigned_to: params.assigned_to,
skip_if_in_workspace: params.skip_if_in_workspace,
skip_if_in_campaign: params.skip_if_in_campaign,
skip_if_in_list: params.skip_if_in_list,
blocklist_id: params.blocklist_id,
verify_leads_for_lead_finder: params.verify_leads_for_lead_finder,
verify_leads_on_import: params.verify_leads_on_import,
custom_variables: params.custom_variables,
}),
},
transformResponse: async (response) => {
const data = await parseInstantlyResponse(response)
const lead = mapLead(data)
return {
success: true,
output: {
lead,
id: lead.id,
email_address: lead.email,
first_name: lead.first_name,
last_name: lead.last_name,
campaign: lead.campaign,
status: lead.status,
},
}
},
outputs: leadOutputs,
}
@@ -0,0 +1,70 @@
import type {
InstantlyCreateLeadListParams,
InstantlyLeadListResponse,
} from '@/tools/instantly/types'
import {
compactBody,
instantlyBaseParamFields,
instantlyHeaders,
instantlyUrl,
leadListOutputs,
mapLeadList,
parseInstantlyResponse,
} from '@/tools/instantly/utils'
import type { ToolConfig } from '@/tools/types'
export const createLeadListTool: ToolConfig<
InstantlyCreateLeadListParams,
InstantlyLeadListResponse
> = {
id: 'instantly_create_lead_list',
name: 'Instantly Create Lead List',
description: 'Creates an Instantly V2 lead list.',
version: '1.0.0',
params: {
...instantlyBaseParamFields,
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Lead list name',
},
has_enrichment_task: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether this list runs enrichment for every added lead',
},
owned_by: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'User ID of the lead list owner',
},
},
request: {
url: () => instantlyUrl('/api/v2/lead-lists'),
method: 'POST',
headers: instantlyHeaders,
body: (params) =>
compactBody({
name: params.name,
has_enrichment_task: params.has_enrichment_task,
owned_by: params.owned_by,
}),
},
transformResponse: async (response) => {
const data = await parseInstantlyResponse(response)
const leadList = mapLeadList(data)
return {
success: true,
output: {
lead_list: leadList,
id: leadList.id,
name: leadList.name,
},
}
},
outputs: leadListOutputs,
}
+82
View File
@@ -0,0 +1,82 @@
import type {
InstantlyDeleteLeadsParams,
InstantlyDeleteLeadsResponse,
} from '@/tools/instantly/types'
import {
asRecord,
compactBody,
instantlyBaseParamFields,
instantlyHeaders,
instantlyUrl,
parseInstantlyResponse,
} from '@/tools/instantly/utils'
import type { ToolConfig } from '@/tools/types'
export const deleteLeadsTool: ToolConfig<InstantlyDeleteLeadsParams, InstantlyDeleteLeadsResponse> =
{
id: 'instantly_delete_leads',
name: 'Instantly Delete Leads',
description: 'Deletes Instantly V2 leads in bulk from a campaign or lead list.',
version: '1.0.0',
params: {
...instantlyBaseParamFields,
campaign_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Campaign ID to delete leads from. Required if list_id is not provided.',
},
list_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Lead list ID to delete leads from. Required if campaign_id is not provided.',
},
status: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Optional lead status filter',
},
ids: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Specific lead IDs to delete',
items: { type: 'string', description: 'Lead ID' },
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of matching leads to delete, up to 10000',
},
},
request: {
url: () => instantlyUrl('/api/v2/leads'),
method: 'DELETE',
headers: instantlyHeaders,
body: (params) =>
compactBody({
campaign_id: params.campaign_id,
list_id: params.list_id,
status: params.status,
ids: params.ids,
limit: params.limit,
}),
},
transformResponse: async (response) => {
const data = await parseInstantlyResponse(response)
const result = asRecord(data)
return {
success: true,
output: {
count: typeof result.count === 'number' ? result.count : null,
},
}
},
outputs: {
count: { type: 'number', description: 'Number of leads deleted', optional: true },
},
}
+49
View File
@@ -0,0 +1,49 @@
import type { InstantlyGetLeadParams, InstantlyLeadResponse } from '@/tools/instantly/types'
import {
instantlyBaseParamFields,
instantlyHeaders,
instantlyUrl,
leadOutputs,
mapLead,
parseInstantlyResponse,
} from '@/tools/instantly/utils'
import type { ToolConfig } from '@/tools/types'
export const getLeadTool: ToolConfig<InstantlyGetLeadParams, InstantlyLeadResponse> = {
id: 'instantly_get_lead',
name: 'Instantly Get Lead',
description: 'Retrieves an Instantly V2 lead by ID.',
version: '1.0.0',
params: {
...instantlyBaseParamFields,
leadId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Lead ID',
},
},
request: {
url: (params) => instantlyUrl(`/api/v2/leads/${params.leadId.trim()}`),
method: 'GET',
headers: instantlyHeaders,
},
transformResponse: async (response) => {
const data = await parseInstantlyResponse(response)
const lead = mapLead(data)
return {
success: true,
output: {
lead,
id: lead.id,
email_address: lead.email,
first_name: lead.first_name,
last_name: lead.last_name,
campaign: lead.campaign,
status: lead.status,
},
}
},
outputs: leadOutputs,
}
+14
View File
@@ -0,0 +1,14 @@
export { activateCampaignTool as instantlyActivateCampaignTool } from '@/tools/instantly/activate_campaign'
export { createCampaignTool as instantlyCreateCampaignTool } from '@/tools/instantly/create_campaign'
export { createLeadTool as instantlyCreateLeadTool } from '@/tools/instantly/create_lead'
export { createLeadListTool as instantlyCreateLeadListTool } from '@/tools/instantly/create_lead_list'
export { deleteLeadsTool as instantlyDeleteLeadsTool } from '@/tools/instantly/delete_leads'
export { getLeadTool as instantlyGetLeadTool } from '@/tools/instantly/get_lead'
export { listCampaignsTool as instantlyListCampaignsTool } from '@/tools/instantly/list_campaigns'
export { listEmailsTool as instantlyListEmailsTool } from '@/tools/instantly/list_emails'
export { listLeadListsTool as instantlyListLeadListsTool } from '@/tools/instantly/list_lead_lists'
export { listLeadsTool as instantlyListLeadsTool } from '@/tools/instantly/list_leads'
export { patchCampaignTool as instantlyPatchCampaignTool } from '@/tools/instantly/patch_campaign'
export { replyToEmailTool as instantlyReplyToEmailTool } from '@/tools/instantly/reply_to_email'
export * from '@/tools/instantly/types'
export { updateLeadInterestStatusTool as instantlyUpdateLeadInterestStatusTool } from '@/tools/instantly/update_lead_interest_status'
@@ -0,0 +1,91 @@
import type {
InstantlyListCampaignsParams,
InstantlyListCampaignsResponse,
} from '@/tools/instantly/types'
import {
campaignsListOutputs,
getItems,
getNextStartingAfter,
instantlyBaseParamFields,
instantlyHeaders,
instantlyUrl,
mapCampaign,
parseInstantlyResponse,
} from '@/tools/instantly/utils'
import type { ToolConfig } from '@/tools/types'
export const listCampaignsTool: ToolConfig<
InstantlyListCampaignsParams,
InstantlyListCampaignsResponse
> = {
id: 'instantly_list_campaigns',
name: 'Instantly List Campaigns',
description: 'Retrieves Instantly V2 campaigns with search, status, tag, and pagination filters.',
version: '1.0.0',
params: {
...instantlyBaseParamFields,
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of campaigns to return, from 1 to 100',
},
starting_after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination cursor from next_starting_after',
},
search: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Search by campaign name',
},
tag_ids: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated campaign tag IDs',
},
ai_sales_agent_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'AI Sales Agent ID filter',
},
status: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Campaign status enum value',
},
},
request: {
url: (params) =>
instantlyUrl('/api/v2/campaigns', {
limit: params.limit,
starting_after: params.starting_after,
search: params.search,
tag_ids: params.tag_ids,
ai_sales_agent_id: params.ai_sales_agent_id,
status: params.status,
}),
method: 'GET',
headers: instantlyHeaders,
},
transformResponse: async (response) => {
const data = await parseInstantlyResponse(response)
const campaigns = getItems(data).map(mapCampaign)
return {
success: true,
output: {
campaigns,
count: campaigns.length,
next_starting_after: getNextStartingAfter(data),
},
}
},
outputs: campaignsListOutputs,
}
+109
View File
@@ -0,0 +1,109 @@
import type {
InstantlyListEmailsParams,
InstantlyListEmailsResponse,
} from '@/tools/instantly/types'
import {
emailsListOutputs,
getItems,
getNextStartingAfter,
instantlyBaseParamFields,
instantlyHeaders,
instantlyUrl,
mapEmail,
parseInstantlyResponse,
} from '@/tools/instantly/utils'
import type { ToolConfig } from '@/tools/types'
export const listEmailsTool: ToolConfig<InstantlyListEmailsParams, InstantlyListEmailsResponse> = {
id: 'instantly_list_emails',
name: 'Instantly List Emails',
description: 'Retrieves Instantly V2 Unibox emails with search and pagination filters.',
version: '1.0.0',
params: {
...instantlyBaseParamFields,
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of emails to return, from 1 to 100',
},
starting_after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination cursor from next_starting_after',
},
search: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Search query, email address, or thread:<thread-id>',
},
campaign_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Campaign ID filter',
},
list_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Lead list ID filter',
},
i_status: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Email interest status filter',
},
eaccount: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sending email account filter',
},
lead: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Lead email address filter',
},
is_unread: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Unread status filter',
},
},
request: {
url: (params) =>
instantlyUrl('/api/v2/emails', {
limit: params.limit,
starting_after: params.starting_after,
search: params.search,
campaign_id: params.campaign_id,
list_id: params.list_id,
i_status: params.i_status,
eaccount: params.eaccount,
lead: params.lead,
is_unread: params.is_unread,
}),
method: 'GET',
headers: instantlyHeaders,
},
transformResponse: async (response) => {
const data = await parseInstantlyResponse(response)
const emails = getItems(data).map(mapEmail)
return {
success: true,
output: {
emails,
count: emails.length,
next_starting_after: getNextStartingAfter(data),
},
}
},
outputs: emailsListOutputs,
}
@@ -0,0 +1,77 @@
import type {
InstantlyListLeadListsParams,
InstantlyListLeadListsResponse,
} from '@/tools/instantly/types'
import {
getItems,
getNextStartingAfter,
instantlyBaseParamFields,
instantlyHeaders,
instantlyUrl,
leadListsListOutputs,
mapLeadList,
parseInstantlyResponse,
} from '@/tools/instantly/utils'
import type { ToolConfig } from '@/tools/types'
export const listLeadListsTool: ToolConfig<
InstantlyListLeadListsParams,
InstantlyListLeadListsResponse
> = {
id: 'instantly_list_lead_lists',
name: 'Instantly List Lead Lists',
description: 'Retrieves Instantly V2 lead lists with search and pagination filters.',
version: '1.0.0',
params: {
...instantlyBaseParamFields,
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of lead lists to return, from 1 to 100',
},
starting_after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Starting-after timestamp cursor',
},
has_enrichment_task: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Filter by enrichment task setting',
},
search: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Search query to filter lead lists by name',
},
},
request: {
url: (params) =>
instantlyUrl('/api/v2/lead-lists', {
limit: params.limit,
starting_after: params.starting_after,
has_enrichment_task: params.has_enrichment_task,
search: params.search,
}),
method: 'GET',
headers: instantlyHeaders,
},
transformResponse: async (response) => {
const data = await parseInstantlyResponse(response)
const leadLists = getItems(data).map(mapLeadList)
return {
success: true,
output: {
lead_lists: leadLists,
count: leadLists.length,
next_starting_after: getNextStartingAfter(data),
},
}
},
outputs: leadListsListOutputs,
}
+168
View File
@@ -0,0 +1,168 @@
import type { InstantlyListLeadsParams, InstantlyListLeadsResponse } from '@/tools/instantly/types'
import {
compactBody,
getItems,
getNextStartingAfter,
instantlyBaseParamFields,
instantlyHeaders,
instantlyUrl,
leadsListOutputs,
mapLead,
parseInstantlyResponse,
} from '@/tools/instantly/utils'
import type { ToolConfig } from '@/tools/types'
export const listLeadsTool: ToolConfig<InstantlyListLeadsParams, InstantlyListLeadsResponse> = {
id: 'instantly_list_leads',
name: 'Instantly List Leads',
description: 'Retrieves Instantly V2 leads with search, campaign, list, and pagination filters.',
version: '1.0.0',
params: {
...instantlyBaseParamFields,
search: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Search by first name, last name, or email',
},
filter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Instantly lead filter value, such as FILTER_VAL_CONTACTED or FILTER_VAL_ACTIVE',
},
campaign: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Campaign ID to filter leads',
},
list_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Lead list ID to filter leads',
},
in_campaign: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether the lead is in a campaign',
},
in_list: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether the lead is in a list',
},
ids: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Lead IDs to include',
items: { type: 'string', description: 'Lead ID' },
},
excluded_ids: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Lead IDs to exclude',
items: { type: 'string', description: 'Lead ID' },
},
organization_user_ids: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Organization user IDs to filter leads',
items: { type: 'string', description: 'Organization user ID' },
},
smart_view_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Smart view ID to filter leads',
},
contacts: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Lead email addresses to include',
items: { type: 'string', description: 'Email address' },
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of leads to return, from 1 to 100',
},
starting_after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Forward pagination cursor from next_starting_after',
},
distinct_contacts: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to return distinct contacts',
},
is_website_visitor: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether the lead is a website visitor',
},
enrichment_status: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Enrichment status filter',
},
esg_code: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Email security gateway code filter',
},
},
request: {
url: () => instantlyUrl('/api/v2/leads/list'),
method: 'POST',
headers: instantlyHeaders,
body: (params) =>
compactBody({
search: params.search,
filter: params.filter,
campaign: params.campaign,
list_id: params.list_id,
in_campaign: params.in_campaign,
in_list: params.in_list,
ids: params.ids,
excluded_ids: params.excluded_ids,
contacts: params.contacts,
limit: params.limit,
starting_after: params.starting_after,
organization_user_ids: params.organization_user_ids,
smart_view_id: params.smart_view_id,
is_website_visitor: params.is_website_visitor,
distinct_contacts: params.distinct_contacts,
enrichment_status: params.enrichment_status,
esg_code: params.esg_code,
}),
},
transformResponse: async (response) => {
const data = await parseInstantlyResponse(response)
const leads = getItems(data).map(mapLead)
return {
success: true,
output: {
leads,
count: leads.length,
next_starting_after: getNextStartingAfter(data),
},
}
},
outputs: leadsListOutputs,
}
+142
View File
@@ -0,0 +1,142 @@
import type {
InstantlyCampaignResponse,
InstantlyPatchCampaignParams,
} from '@/tools/instantly/types'
import {
campaignOutputs,
compactBody,
instantlyBaseParamFields,
instantlyHeaders,
instantlyUrl,
mapCampaign,
parseInstantlyResponse,
} from '@/tools/instantly/utils'
import type { ToolConfig } from '@/tools/types'
export const patchCampaignTool: ToolConfig<
InstantlyPatchCampaignParams,
InstantlyCampaignResponse
> = {
id: 'instantly_patch_campaign',
name: 'Instantly Patch Campaign',
description: 'Updates documented Instantly V2 campaign fields.',
version: '1.0.0',
params: {
...instantlyBaseParamFields,
campaignId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Campaign ID',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Campaign name',
},
campaign_schedule: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'Campaign schedule object with schedules array',
},
sequences: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Campaign sequence definitions',
items: { type: 'object', description: 'Sequence object' },
},
email_list: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Sending email accounts',
items: { type: 'string', description: 'Email address' },
},
daily_limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Daily sending limit',
},
daily_max_leads: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Daily maximum new leads to contact',
},
open_tracking: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to track opens',
},
stop_on_reply: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to stop the campaign on reply',
},
link_tracking: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to track links',
},
text_only: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether the campaign is text only',
},
email_gap: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Gap between emails in minutes',
},
pl_value: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Value of every positive lead',
},
},
request: {
url: (params) => instantlyUrl(`/api/v2/campaigns/${params.campaignId.trim()}`),
method: 'PATCH',
headers: instantlyHeaders,
body: (params) =>
compactBody({
name: params.name,
campaign_schedule: params.campaign_schedule,
sequences: params.sequences,
pl_value: params.pl_value,
email_gap: params.email_gap,
text_only: params.text_only,
email_list: params.email_list,
daily_limit: params.daily_limit,
stop_on_reply: params.stop_on_reply,
link_tracking: params.link_tracking,
open_tracking: params.open_tracking,
daily_max_leads: params.daily_max_leads,
}),
},
transformResponse: async (response) => {
const data = await parseInstantlyResponse(response)
const campaign = mapCampaign(data)
return {
success: true,
output: {
campaign,
id: campaign.id,
name: campaign.name,
status: campaign.status,
},
}
},
outputs: campaignOutputs,
}
@@ -0,0 +1,86 @@
import type { InstantlyEmailResponse, InstantlyReplyToEmailParams } from '@/tools/instantly/types'
import {
compactBody,
emailOutputs,
instantlyBaseParamFields,
instantlyHeaders,
instantlyUrl,
mapEmail,
parseInstantlyResponse,
} from '@/tools/instantly/utils'
import type { ToolConfig } from '@/tools/types'
export const replyToEmailTool: ToolConfig<InstantlyReplyToEmailParams, InstantlyEmailResponse> = {
id: 'instantly_reply_to_email',
name: 'Instantly Reply To Email',
description: 'Sends an Instantly V2 reply to an existing Unibox email.',
version: '1.0.0',
params: {
...instantlyBaseParamFields,
eaccount: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Connected email account used to send the reply',
},
reply_to_uuid: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Email ID to reply to',
},
subject: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Reply subject',
},
body: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'Reply body object with text and/or html',
},
cc_address_email_list: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated CC email addresses',
},
bcc_address_email_list: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated BCC email addresses',
},
},
request: {
url: () => instantlyUrl('/api/v2/emails/reply'),
method: 'POST',
headers: instantlyHeaders,
body: (params) =>
compactBody({
eaccount: params.eaccount,
reply_to_uuid: params.reply_to_uuid,
subject: params.subject,
body: params.body,
cc_address_email_list: params.cc_address_email_list,
bcc_address_email_list: params.bcc_address_email_list,
}),
},
transformResponse: async (response) => {
const data = await parseInstantlyResponse(response)
const email = mapEmail(data)
return {
success: true,
output: {
email,
id: email.id,
subject: email.subject,
thread_id: email.thread_id,
},
}
},
outputs: emailOutputs,
}
+314
View File
@@ -0,0 +1,314 @@
import type { ToolResponse } from '@/tools/types'
export type InstantlyScalar = string | number | boolean | null
export interface InstantlyBaseParams {
apiKey: string
}
export interface InstantlyLead {
id: string | null
timestamp_created: string | null
timestamp_updated: string | null
organization: string | null
campaign: string | null
status: number | null
email: string | null
personalization: string | null
website: string | null
last_name: string | null
first_name: string | null
company_name: string | null
job_title: string | null
phone: string | null
email_open_count: number | null
email_reply_count: number | null
email_click_count: number | null
company_domain: string | null
payload: Record<string, unknown> | null
lt_interest_status: number | null
}
export interface InstantlyCampaign {
id: string | null
name: string | null
pl_value: number | null
status: number | null
is_evergreen: boolean | null
timestamp_created: string | null
timestamp_updated: string | null
email_gap: number | null
daily_limit: number | null
daily_max_leads: number | null
open_tracking: boolean | null
stop_on_reply: boolean | null
sequences: unknown[]
campaign_schedule: Record<string, unknown> | null
}
export interface InstantlyEmail {
id: string | null
timestamp_created: string | null
timestamp_email: string | null
message_id: string | null
subject: string | null
from_address_email: string | null
to_address_email_list: string | null
cc_address_email_list: string | null
bcc_address_email_list: string | null
reply_to: string | null
body: {
text: string | null
html: string | null
}
organization_id: string | null
campaign_id: string | null
subsequence_id: string | null
list_id: string | null
lead: string | null
lead_id: string | null
eaccount: string | null
ue_type: number | null
is_unread: number | null
is_auto_reply: number | null
i_status: number | null
thread_id: string | null
content_preview: string | null
}
export interface InstantlyLeadList {
id: string | null
organization_id: string | null
has_enrichment_task: boolean | null
owned_by: string | null
name: string | null
timestamp_created: string | null
}
export interface InstantlyListLeadsParams extends InstantlyBaseParams {
search?: string
filter?: string
campaign?: string
list_id?: string
in_campaign?: boolean
in_list?: boolean
ids?: string[]
excluded_ids?: string[]
contacts?: string[]
limit?: number
starting_after?: string
organization_user_ids?: string[]
smart_view_id?: string
is_website_visitor?: boolean
distinct_contacts?: boolean
enrichment_status?: number
esg_code?: string
}
export interface InstantlyGetLeadParams extends InstantlyBaseParams {
leadId: string
}
export interface InstantlyCreateLeadParams extends InstantlyBaseParams {
campaign?: string | null
email?: string | null
personalization?: string | null
website?: string | null
last_name?: string | null
first_name?: string | null
company_name?: string | null
job_title?: string | null
phone?: string | null
lt_interest_status?: number
pl_value_lead?: string | null
list_id?: string | null
assigned_to?: string | null
skip_if_in_workspace?: boolean
skip_if_in_campaign?: boolean
skip_if_in_list?: boolean
blocklist_id?: string
verify_leads_for_lead_finder?: boolean
verify_leads_on_import?: boolean
custom_variables?: Record<string, InstantlyScalar>
}
export interface InstantlyDeleteLeadsParams extends InstantlyBaseParams {
campaign_id?: string
list_id?: string
status?: number
ids?: string[]
limit?: number
}
export interface InstantlyUpdateLeadInterestStatusParams extends InstantlyBaseParams {
lead_email: string
interest_value: number | null
campaign_id?: string
ai_interest_value?: number
disable_auto_interest?: boolean
list_id?: string
}
export interface InstantlyListCampaignsParams extends InstantlyBaseParams {
limit?: number
starting_after?: string
search?: string
tag_ids?: string
ai_sales_agent_id?: string
status?: number
}
export interface InstantlyCreateCampaignParams extends InstantlyBaseParams {
name: string
campaign_schedule: Record<string, unknown>
sequences?: unknown[]
pl_value?: number | null
email_gap?: number | null
text_only?: boolean | null
email_list?: string[]
daily_limit?: number | null
stop_on_reply?: boolean | null
link_tracking?: boolean | null
open_tracking?: boolean
daily_max_leads?: number | null
}
export interface InstantlyPatchCampaignParams extends Partial<InstantlyCreateCampaignParams> {
apiKey: string
campaignId: string
}
export interface InstantlyActivateCampaignParams extends InstantlyBaseParams {
campaignId: string
}
export interface InstantlyListEmailsParams extends InstantlyBaseParams {
limit?: number
starting_after?: string
search?: string
campaign_id?: string
list_id?: string
i_status?: number
eaccount?: string
lead?: string
is_unread?: boolean
}
export interface InstantlyReplyToEmailParams extends InstantlyBaseParams {
eaccount: string
reply_to_uuid: string
subject: string
body: {
text?: string
html?: string
}
cc_address_email_list?: string
bcc_address_email_list?: string
}
export interface InstantlyListLeadListsParams extends InstantlyBaseParams {
limit?: number
starting_after?: string
has_enrichment_task?: boolean
search?: string
}
export interface InstantlyCreateLeadListParams extends InstantlyBaseParams {
name: string
has_enrichment_task?: boolean | null
owned_by?: string | null
}
export interface InstantlyListLeadsResponse extends ToolResponse {
output: {
leads: InstantlyLead[]
count: number
next_starting_after: string | null
}
}
export interface InstantlyLeadResponse extends ToolResponse {
output: {
lead: InstantlyLead
id: string | null
email_address: string | null
first_name: string | null
last_name: string | null
campaign: string | null
status: number | null
}
}
export interface InstantlyDeleteLeadsResponse extends ToolResponse {
output: {
count: number | null
}
}
export interface InstantlyUpdateLeadInterestStatusResponse extends ToolResponse {
output: {
message: string | null
}
}
export interface InstantlyListCampaignsResponse extends ToolResponse {
output: {
campaigns: InstantlyCampaign[]
count: number
next_starting_after: string | null
}
}
export interface InstantlyCampaignResponse extends ToolResponse {
output: {
campaign: InstantlyCampaign
id: string | null
name: string | null
status: number | null
}
}
export interface InstantlyListEmailsResponse extends ToolResponse {
output: {
emails: InstantlyEmail[]
count: number
next_starting_after: string | null
}
}
export interface InstantlyEmailResponse extends ToolResponse {
output: {
email: InstantlyEmail
id: string | null
subject: string | null
thread_id: string | null
}
}
export interface InstantlyListLeadListsResponse extends ToolResponse {
output: {
lead_lists: InstantlyLeadList[]
count: number
next_starting_after: string | null
}
}
export interface InstantlyLeadListResponse extends ToolResponse {
output: {
lead_list: InstantlyLeadList
id: string | null
name: string | null
}
}
export type InstantlyResponse =
| InstantlyListLeadsResponse
| InstantlyLeadResponse
| InstantlyDeleteLeadsResponse
| InstantlyUpdateLeadInterestStatusResponse
| InstantlyListCampaignsResponse
| InstantlyCampaignResponse
| InstantlyListEmailsResponse
| InstantlyEmailResponse
| InstantlyListLeadListsResponse
| InstantlyLeadListResponse
@@ -0,0 +1,99 @@
import type {
InstantlyUpdateLeadInterestStatusParams,
InstantlyUpdateLeadInterestStatusResponse,
} from '@/tools/instantly/types'
import {
asRecord,
compactBody,
instantlyBaseParamFields,
instantlyHeaders,
instantlyUrl,
parseInstantlyResponse,
} from '@/tools/instantly/utils'
import type { ToolConfig } from '@/tools/types'
export const updateLeadInterestStatusTool: ToolConfig<
InstantlyUpdateLeadInterestStatusParams,
InstantlyUpdateLeadInterestStatusResponse
> = {
id: 'instantly_update_lead_interest_status',
name: 'Instantly Update Lead Interest Status',
description: 'Submits an Instantly V2 background job to update a lead interest status.',
version: '1.0.0',
params: {
...instantlyBaseParamFields,
lead_email: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Lead email address',
},
interest_value: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Interest status value. Leave empty in the block or pass null to reset to Lead.',
},
campaign_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Campaign ID for the lead',
},
list_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Lead list ID for the lead',
},
ai_interest_value: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'AI interest value to set for the lead',
},
disable_auto_interest: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to disable auto interest',
},
},
request: {
url: () => instantlyUrl('/api/v2/leads/update-interest-status'),
method: 'POST',
headers: instantlyHeaders,
body: (params) => {
if (params.interest_value === undefined) {
throw new Error('Interest Value is required for Instantly Update Lead Interest Status')
}
return compactBody({
lead_email: params.lead_email,
interest_value: params.interest_value,
campaign_id: params.campaign_id,
list_id: params.list_id,
ai_interest_value: params.ai_interest_value,
disable_auto_interest: params.disable_auto_interest,
})
},
},
transformResponse: async (response) => {
const data = await parseInstantlyResponse(response)
const result = asRecord(data)
return {
success: true,
output: {
message: typeof result.message === 'string' ? result.message : null,
},
}
},
outputs: {
message: {
type: 'string',
description: 'Background job submission message',
optional: true,
},
},
}
+366
View File
@@ -0,0 +1,366 @@
import { filterUndefined } from '@sim/utils/object'
import type {
InstantlyCampaign,
InstantlyEmail,
InstantlyLead,
InstantlyLeadList,
} from '@/tools/instantly/types'
import type { ToolConfig } from '@/tools/types'
const INSTANTLY_API_BASE_URL = 'https://api.instantly.ai'
type InstantlyBaseParams = { apiKey: string }
type JsonRecord = Record<string, unknown>
export const instantlyBaseParamFields = {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Instantly API key with the required V2 scopes',
},
} satisfies ToolConfig<InstantlyBaseParams>['params']
export const instantlyHeaders = (params: InstantlyBaseParams) => ({
Authorization: `Bearer ${params.apiKey.trim()}`,
'Content-Type': 'application/json',
})
export function instantlyUrl(path: string, query?: Record<string, unknown>): string {
const url = new URL(path, INSTANTLY_API_BASE_URL)
if (query) {
for (const [key, value] of Object.entries(query)) {
if (value === undefined || value === null || value === '') continue
url.searchParams.append(key, String(value))
}
}
return url.toString()
}
export function compactBody(values: Record<string, unknown>): Record<string, unknown> {
return filterUndefined(values)
}
export async function parseInstantlyResponse(response: Response): Promise<unknown> {
const data = await parseJsonResponse(response)
if (!response.ok) {
throw new Error(
extractInstantlyError(data, `Instantly API request failed (${response.status})`)
)
}
return data
}
export function asRecord(value: unknown): JsonRecord {
return isRecord(value) ? value : {}
}
export function getItems(value: unknown): JsonRecord[] {
const data = asRecord(value)
return Array.isArray(data.items) ? data.items.map(asRecord) : []
}
export function getNextStartingAfter(value: unknown): string | null {
const data = asRecord(value)
return asString(data.next_starting_after)
}
export function mapLead(value: unknown): InstantlyLead {
const lead = asRecord(value)
return {
id: asString(lead.id),
timestamp_created: asString(lead.timestamp_created),
timestamp_updated: asString(lead.timestamp_updated),
organization: asString(lead.organization),
campaign: asString(lead.campaign),
status: asNumber(lead.status),
email: asString(lead.email),
personalization: asString(lead.personalization),
website: asString(lead.website),
last_name: asString(lead.last_name),
first_name: asString(lead.first_name),
company_name: asString(lead.company_name),
job_title: asString(lead.job_title),
phone: asString(lead.phone),
email_open_count: asNumber(lead.email_open_count),
email_reply_count: asNumber(lead.email_reply_count),
email_click_count: asNumber(lead.email_click_count),
company_domain: asString(lead.company_domain),
payload: isRecord(lead.payload) ? lead.payload : null,
lt_interest_status: asNumber(lead.lt_interest_status),
}
}
export function mapCampaign(value: unknown): InstantlyCampaign {
const campaign = asRecord(value)
return {
id: asString(campaign.id),
name: asString(campaign.name),
pl_value: asNumber(campaign.pl_value),
status: asNumber(campaign.status),
is_evergreen: asBoolean(campaign.is_evergreen),
timestamp_created: asString(campaign.timestamp_created),
timestamp_updated: asString(campaign.timestamp_updated),
email_gap: asNumber(campaign.email_gap),
daily_limit: asNumber(campaign.daily_limit),
daily_max_leads: asNumber(campaign.daily_max_leads),
open_tracking: asBoolean(campaign.open_tracking),
stop_on_reply: asBoolean(campaign.stop_on_reply),
sequences: Array.isArray(campaign.sequences) ? campaign.sequences : [],
campaign_schedule: isRecord(campaign.campaign_schedule) ? campaign.campaign_schedule : null,
}
}
export function mapEmail(value: unknown): InstantlyEmail {
const email = asRecord(value)
const body = asRecord(email.body)
return {
id: asString(email.id),
timestamp_created: asString(email.timestamp_created),
timestamp_email: asString(email.timestamp_email),
message_id: asString(email.message_id),
subject: asString(email.subject),
from_address_email: asString(email.from_address_email),
to_address_email_list: asString(email.to_address_email_list),
cc_address_email_list: asString(email.cc_address_email_list),
bcc_address_email_list: asString(email.bcc_address_email_list),
reply_to: asString(email.reply_to),
body: {
text: asString(body.text),
html: asString(body.html),
},
organization_id: asString(email.organization_id),
campaign_id: asString(email.campaign_id),
subsequence_id: asString(email.subsequence_id),
list_id: asString(email.list_id),
lead: asString(email.lead),
lead_id: asString(email.lead_id),
eaccount: asString(email.eaccount),
ue_type: asNumber(email.ue_type),
is_unread: asNumber(email.is_unread),
is_auto_reply: asNumber(email.is_auto_reply),
i_status: asNumber(email.i_status),
thread_id: asString(email.thread_id),
content_preview: asString(email.content_preview),
}
}
export function mapLeadList(value: unknown): InstantlyLeadList {
const leadList = asRecord(value)
return {
id: asString(leadList.id),
organization_id: asString(leadList.organization_id),
has_enrichment_task: asBoolean(leadList.has_enrichment_task),
owned_by: asString(leadList.owned_by),
name: asString(leadList.name),
timestamp_created: asString(leadList.timestamp_created),
}
}
export const leadOutputs = {
lead: {
type: 'object',
description: 'Lead details',
properties: {
id: { type: 'string', description: 'Lead ID', nullable: true },
email: { type: 'string', description: 'Lead email address', nullable: true },
first_name: { type: 'string', description: 'Lead first name', nullable: true },
last_name: { type: 'string', description: 'Lead last name', nullable: true },
company_name: { type: 'string', description: 'Lead company name', nullable: true },
job_title: { type: 'string', description: 'Lead job title', nullable: true },
campaign: { type: 'string', description: 'Campaign ID', nullable: true },
status: { type: 'number', description: 'Lead status', nullable: true },
payload: { type: 'json', description: 'Lead custom variables', nullable: true },
},
},
id: { type: 'string', description: 'Lead ID', optional: true },
email_address: { type: 'string', description: 'Lead email address', optional: true },
first_name: { type: 'string', description: 'Lead first name', optional: true },
last_name: { type: 'string', description: 'Lead last name', optional: true },
campaign: { type: 'string', description: 'Campaign ID', optional: true },
status: { type: 'number', description: 'Lead status', optional: true },
} satisfies ToolConfig['outputs']
export const leadsListOutputs = {
leads: {
type: 'array',
description: 'List of leads',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Lead ID', nullable: true },
email: { type: 'string', description: 'Lead email address', nullable: true },
first_name: { type: 'string', description: 'Lead first name', nullable: true },
last_name: { type: 'string', description: 'Lead last name', nullable: true },
company_name: { type: 'string', description: 'Lead company name', nullable: true },
campaign: { type: 'string', description: 'Campaign ID', nullable: true },
status: { type: 'number', description: 'Lead status', nullable: true },
},
},
},
count: { type: 'number', description: 'Number of leads returned' },
next_starting_after: { type: 'string', description: 'Cursor for the next page', optional: true },
} satisfies ToolConfig['outputs']
export const campaignOutputs = {
campaign: {
type: 'object',
description: 'Campaign details',
properties: {
id: { type: 'string', description: 'Campaign ID', nullable: true },
name: { type: 'string', description: 'Campaign name', nullable: true },
status: { type: 'number', description: 'Campaign status', nullable: true },
daily_limit: { type: 'number', description: 'Daily sending limit', nullable: true },
daily_max_leads: { type: 'number', description: 'Daily max new leads', nullable: true },
open_tracking: {
type: 'boolean',
description: 'Whether open tracking is enabled',
nullable: true,
},
},
},
id: { type: 'string', description: 'Campaign ID', optional: true },
name: { type: 'string', description: 'Campaign name', optional: true },
status: { type: 'number', description: 'Campaign status', optional: true },
} satisfies ToolConfig['outputs']
export const campaignsListOutputs = {
campaigns: {
type: 'array',
description: 'List of campaigns',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Campaign ID', nullable: true },
name: { type: 'string', description: 'Campaign name', nullable: true },
status: { type: 'number', description: 'Campaign status', nullable: true },
daily_limit: { type: 'number', description: 'Daily sending limit', nullable: true },
},
},
},
count: { type: 'number', description: 'Number of campaigns returned' },
next_starting_after: { type: 'string', description: 'Cursor for the next page', optional: true },
} satisfies ToolConfig['outputs']
export const emailOutputs = {
email: {
type: 'object',
description: 'Email details',
properties: {
id: { type: 'string', description: 'Email ID', nullable: true },
subject: { type: 'string', description: 'Email subject', nullable: true },
from_address_email: { type: 'string', description: 'Sender email', nullable: true },
to_address_email_list: {
type: 'string',
description: 'Recipient email list',
nullable: true,
},
thread_id: { type: 'string', description: 'Thread ID', nullable: true },
content_preview: { type: 'string', description: 'Email content preview', nullable: true },
},
},
id: { type: 'string', description: 'Email ID', optional: true },
subject: { type: 'string', description: 'Email subject', optional: true },
thread_id: { type: 'string', description: 'Thread ID', optional: true },
} satisfies ToolConfig['outputs']
export const emailsListOutputs = {
emails: {
type: 'array',
description: 'List of emails',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Email ID', nullable: true },
subject: { type: 'string', description: 'Email subject', nullable: true },
from_address_email: { type: 'string', description: 'Sender email', nullable: true },
lead: { type: 'string', description: 'Lead email', nullable: true },
thread_id: { type: 'string', description: 'Thread ID', nullable: true },
},
},
},
count: { type: 'number', description: 'Number of emails returned' },
next_starting_after: { type: 'string', description: 'Cursor for the next page', optional: true },
} satisfies ToolConfig['outputs']
export const leadListOutputs = {
lead_list: {
type: 'object',
description: 'Lead list details',
properties: {
id: { type: 'string', description: 'Lead list ID', nullable: true },
organization_id: { type: 'string', description: 'Organization ID', nullable: true },
has_enrichment_task: {
type: 'boolean',
description: 'Whether enrichment is enabled',
nullable: true,
},
owned_by: { type: 'string', description: 'Owner user ID', nullable: true },
name: { type: 'string', description: 'Lead list name', nullable: true },
timestamp_created: { type: 'string', description: 'Creation timestamp', nullable: true },
},
},
id: { type: 'string', description: 'Lead list ID', optional: true },
name: { type: 'string', description: 'Lead list name', optional: true },
} satisfies ToolConfig['outputs']
export const leadListsListOutputs = {
lead_lists: {
type: 'array',
description: 'List of lead lists',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Lead list ID', nullable: true },
name: { type: 'string', description: 'Lead list name', nullable: true },
has_enrichment_task: {
type: 'boolean',
description: 'Whether enrichment is enabled',
nullable: true,
},
timestamp_created: { type: 'string', description: 'Creation timestamp', nullable: true },
},
},
},
count: { type: 'number', description: 'Number of lead lists returned' },
next_starting_after: { type: 'string', description: 'Cursor for the next page', optional: true },
} satisfies ToolConfig['outputs']
function isRecord(value: unknown): value is JsonRecord {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
async function parseJsonResponse(response: Response): Promise<unknown> {
try {
return await response.json()
} catch {
return null
}
}
function extractInstantlyError(value: unknown, fallback: string): string {
const data = asRecord(value)
if (typeof data.message === 'string') return data.message
if (typeof data.error === 'string') return data.error
return fallback
}
function asString(value: unknown): string | null {
return typeof value === 'string' ? value : null
}
function asNumber(value: unknown): number | null {
return typeof value === 'number' && Number.isFinite(value) ? value : null
}
function asBoolean(value: unknown): boolean | null {
return typeof value === 'boolean' ? value : null
}
+28
View File
@@ -1390,6 +1390,21 @@ import {
infisicalListSecretsTool,
infisicalUpdateSecretTool,
} from '@/tools/infisical'
import {
instantlyActivateCampaignTool,
instantlyCreateCampaignTool,
instantlyCreateLeadListTool,
instantlyCreateLeadTool,
instantlyDeleteLeadsTool,
instantlyGetLeadTool,
instantlyListCampaignsTool,
instantlyListEmailsTool,
instantlyListLeadListsTool,
instantlyListLeadsTool,
instantlyPatchCampaignTool,
instantlyReplyToEmailTool,
instantlyUpdateLeadInterestStatusTool,
} from '@/tools/instantly'
import {
intercomAssignConversationV2Tool,
intercomAttachContactToCompanyV2Tool,
@@ -3434,6 +3449,19 @@ export const tools: Record<string, ToolConfig> = {
hex_list_users: hexListUsersTool,
hex_run_project: hexRunProjectTool,
hex_update_project: hexUpdateProjectTool,
instantly_activate_campaign: instantlyActivateCampaignTool,
instantly_create_campaign: instantlyCreateCampaignTool,
instantly_create_lead: instantlyCreateLeadTool,
instantly_create_lead_list: instantlyCreateLeadListTool,
instantly_delete_leads: instantlyDeleteLeadsTool,
instantly_get_lead: instantlyGetLeadTool,
instantly_list_campaigns: instantlyListCampaignsTool,
instantly_list_emails: instantlyListEmailsTool,
instantly_list_lead_lists: instantlyListLeadListsTool,
instantly_list_leads: instantlyListLeadsTool,
instantly_patch_campaign: instantlyPatchCampaignTool,
instantly_reply_to_email: instantlyReplyToEmailTool,
instantly_update_lead_interest_status: instantlyUpdateLeadInterestStatusTool,
jina_read_url: jinaReadUrlTool,
jina_search: jinaSearchTool,
ketch_get_consent: ketchGetConsentTool,
@@ -0,0 +1,8 @@
import { createInstantlyTrigger } from '@/triggers/instantly/trigger'
export const instantlyAccountErrorTrigger = createInstantlyTrigger({
id: 'instantly_account_error',
name: 'Instantly Account Error',
description: 'Trigger when Instantly reports an account-level error',
eventLabel: 'Account Error',
})
@@ -0,0 +1,8 @@
import { createInstantlyTrigger } from '@/triggers/instantly/trigger'
export const instantlyAutoReplyReceivedTrigger = createInstantlyTrigger({
id: 'instantly_auto_reply_received',
name: 'Instantly Auto Reply Received',
description: 'Trigger when Instantly receives an auto-reply from a lead',
eventLabel: 'Auto Reply Received',
})
@@ -0,0 +1,8 @@
import { createInstantlyTrigger } from '@/triggers/instantly/trigger'
export const instantlyCampaignCompletedTrigger = createInstantlyTrigger({
id: 'instantly_campaign_completed',
name: 'Instantly Campaign Completed',
description: 'Trigger when an Instantly campaign completes',
eventLabel: 'Campaign Completed',
})
@@ -0,0 +1,8 @@
import { createInstantlyTrigger } from '@/triggers/instantly/trigger'
export const instantlyEmailBouncedTrigger = createInstantlyTrigger({
id: 'instantly_email_bounced',
name: 'Instantly Email Bounced',
description: 'Trigger when an Instantly email bounces',
eventLabel: 'Email Bounced',
})
@@ -0,0 +1,8 @@
import { createInstantlyTrigger } from '@/triggers/instantly/trigger'
export const instantlyEmailOpenedTrigger = createInstantlyTrigger({
id: 'instantly_email_opened',
name: 'Instantly Email Opened',
description: 'Trigger when a lead opens an Instantly email',
eventLabel: 'Email Opened',
})
@@ -0,0 +1,8 @@
import { createInstantlyTrigger } from '@/triggers/instantly/trigger'
export const instantlyEmailSentTrigger = createInstantlyTrigger({
id: 'instantly_email_sent',
name: 'Instantly Email Sent',
description: 'Trigger when Instantly sends an email',
eventLabel: 'Email Sent',
})
+20
View File
@@ -0,0 +1,20 @@
export { instantlyAccountErrorTrigger } from '@/triggers/instantly/account_error'
export { instantlyAutoReplyReceivedTrigger } from '@/triggers/instantly/auto_reply_received'
export { instantlyCampaignCompletedTrigger } from '@/triggers/instantly/campaign_completed'
export { instantlyEmailBouncedTrigger } from '@/triggers/instantly/email_bounced'
export { instantlyEmailOpenedTrigger } from '@/triggers/instantly/email_opened'
export { instantlyEmailSentTrigger } from '@/triggers/instantly/email_sent'
export { instantlyLeadClosedTrigger } from '@/triggers/instantly/lead_closed'
export { instantlyLeadInterestedTrigger } from '@/triggers/instantly/lead_interested'
export { instantlyLeadMeetingBookedTrigger } from '@/triggers/instantly/lead_meeting_booked'
export { instantlyLeadMeetingCompletedTrigger } from '@/triggers/instantly/lead_meeting_completed'
export { instantlyLeadNeutralTrigger } from '@/triggers/instantly/lead_neutral'
export { instantlyLeadNoShowTrigger } from '@/triggers/instantly/lead_no_show'
export { instantlyLeadNotInterestedTrigger } from '@/triggers/instantly/lead_not_interested'
export { instantlyLeadOutOfOfficeTrigger } from '@/triggers/instantly/lead_out_of_office'
export { instantlyLeadUnsubscribedTrigger } from '@/triggers/instantly/lead_unsubscribed'
export { instantlyLeadWrongPersonTrigger } from '@/triggers/instantly/lead_wrong_person'
export { instantlyLinkClickedTrigger } from '@/triggers/instantly/link_clicked'
export { instantlyReplyReceivedTrigger } from '@/triggers/instantly/reply_received'
export { instantlySupersearchEnrichmentCompletedTrigger } from '@/triggers/instantly/supersearch_enrichment_completed'
export { instantlyWebhookTrigger } from '@/triggers/instantly/webhook'
@@ -0,0 +1,8 @@
import { createInstantlyTrigger } from '@/triggers/instantly/trigger'
export const instantlyLeadClosedTrigger = createInstantlyTrigger({
id: 'instantly_lead_closed',
name: 'Instantly Lead Closed',
description: 'Trigger when an Instantly lead is marked closed',
eventLabel: 'Lead Closed',
})
@@ -0,0 +1,8 @@
import { createInstantlyTrigger } from '@/triggers/instantly/trigger'
export const instantlyLeadInterestedTrigger = createInstantlyTrigger({
id: 'instantly_lead_interested',
name: 'Instantly Lead Interested',
description: 'Trigger when an Instantly lead is marked interested',
eventLabel: 'Lead Interested',
})
@@ -0,0 +1,8 @@
import { createInstantlyTrigger } from '@/triggers/instantly/trigger'
export const instantlyLeadMeetingBookedTrigger = createInstantlyTrigger({
id: 'instantly_lead_meeting_booked',
name: 'Instantly Lead Meeting Booked',
description: 'Trigger when an Instantly lead books a meeting',
eventLabel: 'Lead Meeting Booked',
})
@@ -0,0 +1,8 @@
import { createInstantlyTrigger } from '@/triggers/instantly/trigger'
export const instantlyLeadMeetingCompletedTrigger = createInstantlyTrigger({
id: 'instantly_lead_meeting_completed',
name: 'Instantly Lead Meeting Completed',
description: 'Trigger when an Instantly lead completes a meeting',
eventLabel: 'Lead Meeting Completed',
})
@@ -0,0 +1,8 @@
import { createInstantlyTrigger } from '@/triggers/instantly/trigger'
export const instantlyLeadNeutralTrigger = createInstantlyTrigger({
id: 'instantly_lead_neutral',
name: 'Instantly Lead Neutral',
description: 'Trigger when an Instantly lead is marked neutral',
eventLabel: 'Lead Neutral',
})
@@ -0,0 +1,8 @@
import { createInstantlyTrigger } from '@/triggers/instantly/trigger'
export const instantlyLeadNoShowTrigger = createInstantlyTrigger({
id: 'instantly_lead_no_show',
name: 'Instantly Lead No Show',
description: 'Trigger when an Instantly lead is marked no show',
eventLabel: 'Lead No Show',
})
@@ -0,0 +1,8 @@
import { createInstantlyTrigger } from '@/triggers/instantly/trigger'
export const instantlyLeadNotInterestedTrigger = createInstantlyTrigger({
id: 'instantly_lead_not_interested',
name: 'Instantly Lead Not Interested',
description: 'Trigger when an Instantly lead is marked not interested',
eventLabel: 'Lead Not Interested',
})
@@ -0,0 +1,8 @@
import { createInstantlyTrigger } from '@/triggers/instantly/trigger'
export const instantlyLeadOutOfOfficeTrigger = createInstantlyTrigger({
id: 'instantly_lead_out_of_office',
name: 'Instantly Lead Out Of Office',
description: 'Trigger when an Instantly lead is out of office',
eventLabel: 'Lead Out Of Office',
})
@@ -0,0 +1,8 @@
import { createInstantlyTrigger } from '@/triggers/instantly/trigger'
export const instantlyLeadUnsubscribedTrigger = createInstantlyTrigger({
id: 'instantly_lead_unsubscribed',
name: 'Instantly Lead Unsubscribed',
description: 'Trigger when an Instantly lead unsubscribes',
eventLabel: 'Lead Unsubscribed',
})
@@ -0,0 +1,8 @@
import { createInstantlyTrigger } from '@/triggers/instantly/trigger'
export const instantlyLeadWrongPersonTrigger = createInstantlyTrigger({
id: 'instantly_lead_wrong_person',
name: 'Instantly Lead Wrong Person',
description: 'Trigger when an Instantly lead is marked wrong person',
eventLabel: 'Lead Wrong Person',
})
@@ -0,0 +1,8 @@
import { createInstantlyTrigger } from '@/triggers/instantly/trigger'
export const instantlyLinkClickedTrigger = createInstantlyTrigger({
id: 'instantly_link_clicked',
name: 'Instantly Link Clicked',
description: 'Trigger when a lead clicks a tracked Instantly link',
eventLabel: 'Link Clicked',
})
@@ -0,0 +1,8 @@
import { createInstantlyTrigger } from '@/triggers/instantly/trigger'
export const instantlyReplyReceivedTrigger = createInstantlyTrigger({
id: 'instantly_reply_received',
name: 'Instantly Reply Received',
description: 'Trigger when a lead replies to an Instantly email',
eventLabel: 'Reply Received',
})
@@ -0,0 +1,8 @@
import { createInstantlyTrigger } from '@/triggers/instantly/trigger'
export const instantlySupersearchEnrichmentCompletedTrigger = createInstantlyTrigger({
id: 'instantly_supersearch_enrichment_completed',
name: 'Instantly Supersearch Enrichment Completed',
description: 'Trigger when Instantly completes a Supersearch enrichment',
eventLabel: 'Supersearch Enrichment Completed',
})
+43
View File
@@ -0,0 +1,43 @@
import { InstantlyIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildInstantlyExtraFields,
buildInstantlyOutputs,
instantlySetupInstructions,
instantlyTriggerOptions,
} from '@/triggers/instantly/utils'
import type { TriggerConfig } from '@/triggers/types'
interface CreateInstantlyTriggerOptions {
id: string
name: string
description: string
eventLabel: string
includeDropdown?: boolean
}
export function createInstantlyTrigger({
id,
name,
description,
eventLabel,
includeDropdown = false,
}: CreateInstantlyTriggerOptions): TriggerConfig {
return {
id,
name,
provider: 'instantly',
description,
version: '1.0.0',
icon: InstantlyIcon,
subBlocks: buildTriggerSubBlocks({
triggerId: id,
triggerOptions: instantlyTriggerOptions,
includeDropdown,
setupInstructions: instantlySetupInstructions(eventLabel),
extraFields: buildInstantlyExtraFields(id),
}),
outputs: buildInstantlyOutputs(),
webhook: { method: 'POST', headers: { 'Content-Type': 'application/json' } },
}
}
+152
View File
@@ -0,0 +1,152 @@
import type { SubBlockConfig } from '@/blocks/types'
import type { TriggerOutput } from '@/triggers/types'
export const INSTANTLY_TRIGGER_TO_EVENT_TYPE = {
instantly_webhook: 'all_events',
instantly_email_sent: 'email_sent',
instantly_email_opened: 'email_opened',
instantly_reply_received: 'reply_received',
instantly_auto_reply_received: 'auto_reply_received',
instantly_link_clicked: 'link_clicked',
instantly_email_bounced: 'email_bounced',
instantly_lead_unsubscribed: 'lead_unsubscribed',
instantly_account_error: 'account_error',
instantly_campaign_completed: 'campaign_completed',
instantly_lead_neutral: 'lead_neutral',
instantly_lead_interested: 'lead_interested',
instantly_lead_not_interested: 'lead_not_interested',
instantly_lead_meeting_booked: 'lead_meeting_booked',
instantly_lead_meeting_completed: 'lead_meeting_completed',
instantly_lead_closed: 'lead_closed',
instantly_lead_out_of_office: 'lead_out_of_office',
instantly_lead_wrong_person: 'lead_wrong_person',
instantly_lead_no_show: 'lead_no_show',
instantly_supersearch_enrichment_completed: 'supersearch_enrichment_completed',
} as const
export const INSTANTLY_TRIGGER_TO_SUBSCRIPTION_EVENT_TYPE = {
...INSTANTLY_TRIGGER_TO_EVENT_TYPE,
instantly_auto_reply_received: 'all_events',
instantly_link_clicked: 'email_link_clicked',
} as const
export const instantlyTriggerOptions = [
{ label: 'All Events', id: 'instantly_webhook' },
{ label: 'Email Sent', id: 'instantly_email_sent' },
{ label: 'Email Opened', id: 'instantly_email_opened' },
{ label: 'Reply Received', id: 'instantly_reply_received' },
{ label: 'Auto Reply Received', id: 'instantly_auto_reply_received' },
{ label: 'Link Clicked', id: 'instantly_link_clicked' },
{ label: 'Email Bounced', id: 'instantly_email_bounced' },
{ label: 'Lead Unsubscribed', id: 'instantly_lead_unsubscribed' },
{ label: 'Account Error', id: 'instantly_account_error' },
{ label: 'Campaign Completed', id: 'instantly_campaign_completed' },
{ label: 'Lead Neutral', id: 'instantly_lead_neutral' },
{ label: 'Lead Interested', id: 'instantly_lead_interested' },
{ label: 'Lead Not Interested', id: 'instantly_lead_not_interested' },
{ label: 'Lead Meeting Booked', id: 'instantly_lead_meeting_booked' },
{ label: 'Lead Meeting Completed', id: 'instantly_lead_meeting_completed' },
{ label: 'Lead Closed', id: 'instantly_lead_closed' },
{ label: 'Lead Out Of Office', id: 'instantly_lead_out_of_office' },
{ label: 'Lead Wrong Person', id: 'instantly_lead_wrong_person' },
{ label: 'Lead No Show', id: 'instantly_lead_no_show' },
{
label: 'Supersearch Enrichment Completed',
id: 'instantly_supersearch_enrichment_completed',
},
]
export function instantlySetupInstructions(eventType: string): string {
const instructions = [
'Enter an <strong>Instantly API Key</strong> with webhook create/delete permissions.',
'Optionally enter a <strong>Campaign ID</strong> to receive only events for that campaign.',
`Click <strong>Save Configuration</strong> to automatically create an Instantly webhook for <strong>${eventType}</strong>.`,
'The webhook will be automatically deleted from Instantly when this trigger is removed.',
]
return instructions
.map(
(instruction, index) =>
`<div class="mb-3"><strong>${index + 1}.</strong> ${instruction}</div>`
)
.join('')
}
export function buildInstantlyExtraFields(triggerId: string): SubBlockConfig[] {
return [
{
id: 'triggerApiKey',
title: 'API Key',
type: 'short-input',
placeholder: 'Enter your Instantly API key',
password: true,
required: true,
paramVisibility: 'user-only',
mode: 'trigger',
condition: { field: 'selectedTriggerId', value: triggerId },
},
{
id: 'triggerCampaignId',
title: 'Campaign ID (Optional)',
type: 'short-input',
placeholder: 'Leave empty for all campaigns',
paramVisibility: 'user-only',
mode: 'trigger',
condition: { field: 'selectedTriggerId', value: triggerId },
},
]
}
export function buildInstantlyOutputs(): Record<string, TriggerOutput> {
return {
timestamp: { type: 'string', description: 'ISO timestamp when the event occurred' },
eventType: { type: 'string', description: 'Instantly webhook event type' },
workspace: { type: 'string', description: 'Instantly workspace UUID' },
campaignId: { type: 'string', description: 'Instantly campaign UUID' },
campaignName: { type: 'string', description: 'Instantly campaign name' },
leadEmail: { type: 'string', description: 'Lead email address' },
emailAccount: { type: 'string', description: 'Email account used to send the message' },
uniboxUrl: { type: 'string', description: 'URL to view the conversation in Unibox' },
step: { type: 'number', description: 'Campaign step number, starting at 1' },
variant: { type: 'number', description: 'Campaign step variant number, starting at 1' },
isFirst: { type: 'boolean', description: 'Whether this is the first event of this type' },
emailId: { type: 'string', description: 'Email ID, usable as reply_to_uuid' },
emailSubject: { type: 'string', description: 'Sent email subject' },
emailText: { type: 'string', description: 'Sent email plain-text content' },
emailHtml: { type: 'string', description: 'Sent email HTML content' },
replyTextSnippet: { type: 'string', description: 'Short preview of the reply content' },
replySubject: { type: 'string', description: 'Reply email subject' },
replyText: { type: 'string', description: 'Full plain-text reply content' },
replyHtml: { type: 'string', description: 'Full HTML reply content' },
payload: {
type: 'json',
description: 'Full Instantly webhook payload, including any extra lead data fields',
},
}
}
export function getInstantlyEventTypeForTrigger(triggerId: string): string | undefined {
return INSTANTLY_TRIGGER_TO_EVENT_TYPE[triggerId as keyof typeof INSTANTLY_TRIGGER_TO_EVENT_TYPE]
}
export function getInstantlySubscriptionEventTypeForTrigger(triggerId: string): string | undefined {
return INSTANTLY_TRIGGER_TO_SUBSCRIPTION_EVENT_TYPE[
triggerId as keyof typeof INSTANTLY_TRIGGER_TO_SUBSCRIPTION_EVENT_TYPE
]
}
export function isInstantlyEventMatch(triggerId: string, body: Record<string, unknown>): boolean {
if (triggerId === 'instantly_webhook') return true
const expectedEventType = getInstantlyEventTypeForTrigger(triggerId)
if (!expectedEventType) return false
const actualEventType = body.event_type
if (typeof actualEventType !== 'string') return false
if (triggerId === 'instantly_link_clicked') {
return actualEventType === 'link_clicked' || actualEventType === 'email_link_clicked'
}
return actualEventType === expectedEventType
}
+9
View File
@@ -0,0 +1,9 @@
import { createInstantlyTrigger } from '@/triggers/instantly/trigger'
export const instantlyWebhookTrigger = createInstantlyTrigger({
id: 'instantly_webhook',
name: 'Instantly Webhook',
description: 'Trigger workflow on any Instantly webhook event',
eventLabel: 'All Events',
includeDropdown: true,
})
+42
View File
@@ -147,6 +147,28 @@ import {
} from '@/triggers/greenhouse'
import { hubspotPollingTrigger } from '@/triggers/hubspot'
import { imapPollingTrigger } from '@/triggers/imap'
import {
instantlyAccountErrorTrigger,
instantlyAutoReplyReceivedTrigger,
instantlyCampaignCompletedTrigger,
instantlyEmailBouncedTrigger,
instantlyEmailOpenedTrigger,
instantlyEmailSentTrigger,
instantlyLeadClosedTrigger,
instantlyLeadInterestedTrigger,
instantlyLeadMeetingBookedTrigger,
instantlyLeadMeetingCompletedTrigger,
instantlyLeadNeutralTrigger,
instantlyLeadNoShowTrigger,
instantlyLeadNotInterestedTrigger,
instantlyLeadOutOfOfficeTrigger,
instantlyLeadUnsubscribedTrigger,
instantlyLeadWrongPersonTrigger,
instantlyLinkClickedTrigger,
instantlyReplyReceivedTrigger,
instantlySupersearchEnrichmentCompletedTrigger,
instantlyWebhookTrigger,
} from '@/triggers/instantly'
import {
intercomContactCreatedTrigger,
intercomConversationClosedTrigger,
@@ -563,6 +585,26 @@ export const TRIGGER_REGISTRY: TriggerRegistry = {
intercom_contact_created: intercomContactCreatedTrigger,
intercom_user_created: intercomUserCreatedTrigger,
intercom_webhook: intercomWebhookTrigger,
instantly_webhook: instantlyWebhookTrigger,
instantly_email_sent: instantlyEmailSentTrigger,
instantly_email_opened: instantlyEmailOpenedTrigger,
instantly_reply_received: instantlyReplyReceivedTrigger,
instantly_auto_reply_received: instantlyAutoReplyReceivedTrigger,
instantly_link_clicked: instantlyLinkClickedTrigger,
instantly_email_bounced: instantlyEmailBouncedTrigger,
instantly_lead_unsubscribed: instantlyLeadUnsubscribedTrigger,
instantly_account_error: instantlyAccountErrorTrigger,
instantly_campaign_completed: instantlyCampaignCompletedTrigger,
instantly_lead_neutral: instantlyLeadNeutralTrigger,
instantly_lead_interested: instantlyLeadInterestedTrigger,
instantly_lead_not_interested: instantlyLeadNotInterestedTrigger,
instantly_lead_meeting_booked: instantlyLeadMeetingBookedTrigger,
instantly_lead_meeting_completed: instantlyLeadMeetingCompletedTrigger,
instantly_lead_closed: instantlyLeadClosedTrigger,
instantly_lead_out_of_office: instantlyLeadOutOfOfficeTrigger,
instantly_lead_wrong_person: instantlyLeadWrongPersonTrigger,
instantly_lead_no_show: instantlyLeadNoShowTrigger,
instantly_supersearch_enrichment_completed: instantlySupersearchEnrichmentCompletedTrigger,
zoom_meeting_started: zoomMeetingStartedTrigger,
zoom_meeting_ended: zoomMeetingEndedTrigger,
zoom_participant_joined: zoomParticipantJoinedTrigger,