feat(tools): added smtp, sendgrid, mailgun, linkedin, fixed permissions in context menu (#2133)

* feat(tools): added twilio sendgrid integration

* feat(tools): added smtp, sendgrid, mailgun, fixed permissions in context menu

* added top level mocks for sporadically failing tests

* incr type safety
This commit is contained in:
Waleed
2025-11-29 17:58:42 -08:00
committed by GitHub
parent 21a640af50
commit 7bf9251db1
107 changed files with 6379 additions and 127 deletions

View File

@@ -1464,11 +1464,17 @@ export function DiscordIcon(props: SVGProps<SVGSVGElement>) {
export function LinkedInIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='4 4 42 42' width='1em' height='1em'>
<svg {...props} height='72' viewBox='0 0 72 72' width='72' xmlns='http://www.w3.org/2000/svg'>
<g fill='none' fillRule='evenodd'>
<path
fill='currentColor'
d='M41,4H9C6.24,4,4,6.24,4,9v32c0,2.76,2.24,5,5,5h32c2.76,0,5-2.24,5-5V9C46,6.24,43.76,4,41,4z M17,20v19h-6V20H17z M11,14.47c0-1.4,1.2-2.47,3-2.47s2.93,1.07,3,2.47c0,1.4-1.12,2.53-3,2.53C12.2,17,11,15.87,11,14.47z M39,39h-6c0,0,0-9.26,0-10 c0-2-1-4-3.5-4.04h-0.08C27,24.96,26,27.02,26,29c0,0.91,0,10,0,10h-6V20h6v2.56c0,0,1.93-2.56,5.81-2.56 c3.97,0,7.19,2.73,7.19,8.26V39z'
d='M8,72 L64,72 C68.418278,72 72,68.418278 72,64 L72,8 C72,3.581722 68.418278,-8.11624501e-16 64,0 L8,0 C3.581722,8.11624501e-16 -5.41083001e-16,3.581722 0,8 L0,64 C5.41083001e-16,68.418278 3.581722,72 8,72 Z'
fill='#0072B1'
/>
<path
d='M62,62 L51.315625,62 L51.315625,43.8021149 C51.315625,38.8127542 49.4197917,36.0245323 45.4707031,36.0245323 C41.1746094,36.0245323 38.9300781,38.9261103 38.9300781,43.8021149 L38.9300781,62 L28.6333333,62 L28.6333333,27.3333333 L38.9300781,27.3333333 L38.9300781,32.0029283 C38.9300781,32.0029283 42.0260417,26.2742151 49.3825521,26.2742151 C56.7356771,26.2742151 62,30.7644705 62,40.051212 L62,62 Z M16.349349,22.7940133 C12.8420573,22.7940133 10,19.9296567 10,16.3970067 C10,12.8643566 12.8420573,10 16.349349,10 C19.8566406,10 22.6970052,12.8643566 22.6970052,16.3970067 C22.6970052,19.9296567 19.8566406,22.7940133 16.349349,22.7940133 Z M11.0325521,62 L21.769401,62 L21.769401,27.3333333 L11.0325521,27.3333333 L11.0325521,62 Z'
fill='#FFF'
/>
</g>
</svg>
)
}
@@ -4344,3 +4350,81 @@ export function PylonIcon(props: SVGProps<SVGSVGElement>) {
</svg>
)
}
export function SendgridIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
width='800px'
height='800px'
viewBox='0 0 256 256'
version='1.1'
xmlns='http://www.w3.org/2000/svg'
xmlnsXlink='http://www.w3.org/1999/xlink'
preserveAspectRatio='xMidYMid'
>
<g>
<path
d='M256.000405,0 L256.000405,170.666936 L170.666936,170.666936 L170.666936,255.996382 L0.00201096905,255.996382 L0.002,170.666 L0,170.666936 L0,85.3314569 L85.3334681,85.3314569 L85.3334681,0 L256.000405,0 Z'
fill='#9DD6E3'
/>
<polygon
fill='#3F72AB'
points='0.00201096905 255.996382 85.3354791 255.996382 85.3354791 170.662915 0.00201096905 170.662915'
/>
<polygon
fill='#00A9D1'
points='170.666936 170.666936 256.000405 170.666936 256.000405 85.3314569 170.666936 85.3314569'
/>
<polygon
fill='#00A9D1'
points='85.3334681 85.3334679 170.666936 85.3334679 170.666936 0 85.3334681 0'
/>
<polygon
fill='#2191C4'
points='85.3334681 170.664925 170.666936 170.664925 170.666936 85.3314569 85.3334681 85.3314569'
/>
<polygon
fill='#3F72AB'
points='170.666936 85.3334679 256.000405 85.3334679 256.000405 0 170.666936 0'
/>
</g>
</svg>
)
}
export function MailgunIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
fill='currentColor'
xmlns='http://www.w3.org/2000/svg'
xmlSpace='preserve'
viewBox='0 0 512 512'
>
<path d='M256.5 159.5c-53.5 0-97 43.5-97 97s43.5 97 97 97 97-43.5 97-97-43.5-97-97-97m-151.1 97c0-83.4 67.7-151.1 151.1-151.1s151.1 67.7 151.1 151.1c0 5.8-.5 11-1 16.3-1 14.7 9.4 25.7 24.1 25.7 24.7 0 27.3-32 27.3-42.5 0-111.7-90.2-202-202-202S54 144.3 54 256s90.2 202 202 202c59.3 0 112.3-25.7 149.5-66.1l41.4 34.6C400.3 479 332.1 512 256 512 114.4 512 0 397.1 0 256 0 114.4 114.9 0 256 0c141.6 0 256 114.9 256 256 0 56.7-27.3 102.8-81.3 102.8-24.1 0-38.3-11-46.7-23.1-26.8 43-74 71.3-128.5 71.3-82.4.6-150.1-67.1-150.1-150.5m151.1-44.6c24.7 0 44.6 19.9 44.6 44.1 0 24.7-19.9 44.6-44.6 44.6s-44.6-19.9-44.6-44.6c.6-24.1 20-44.1 44.6-44.1' />
</svg>
)
}
export function SmtpIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
width='30'
height='24'
viewBox='0 0 30 24'
fill='none'
xmlns='http://www.w3.org/2000/svg'
>
<path
d='M2.35742 5.83288L11.7674 12.1071C13.0656 12.9712 13.7141 13.404 14.4151 13.5725C15.0352 13.7208 15.681 13.7208 16.2998 13.5725C17.0008 13.404 17.6492 12.9712 18.9475 12.1071L28.3574 5.83288M8.82844 21.7219H21.8864C24.1513 21.7219 25.2837 21.7219 26.1492 21.2811C26.9097 20.8931 27.5278 20.2744 27.9152 19.5137C28.3574 18.6482 28.3574 17.5158 28.3574 15.2509V7.97102C28.3574 5.70616 28.3574 4.57373 27.9166 3.70823C27.5288 2.94727 26.9102 2.32858 26.1492 1.94084C25.2837 1.5 24.1513 1.5 21.8864 1.5H8.82844C6.56358 1.5 5.43115 1.5 4.56566 1.94084C3.80519 2.32881 3.187 2.94747 2.79961 3.70823C2.35742 4.57373 2.35742 5.70616 2.35742 7.97102V15.2509C2.35742 17.5158 2.35742 18.6482 2.79826 19.5137C3.186 20.2747 3.80469 20.8933 4.56566 21.2811C5.43115 21.7219 6.56358 21.7219 8.82844 21.7219Z'
stroke='currentColor'
strokeWidth='2.5'
strokeLinecap='round'
strokeLinejoin='round'
/>
<circle cx='24' cy='6' r='4' fill='currentColor' stroke='none' />
</svg>
)
}

View File

@@ -37,8 +37,10 @@ import {
JinaAIIcon,
JiraIcon,
LinearIcon,
LinkedInIcon,
LinkupIcon,
MailchimpIcon,
MailgunIcon,
Mem0Icon,
MicrosoftExcelIcon,
MicrosoftOneDriveIcon,
@@ -65,9 +67,11 @@ import {
ResendIcon,
S3Icon,
SalesforceIcon,
SendgridIcon,
SentryIcon,
SerperIcon,
SlackIcon,
SmtpIcon,
STTIcon,
StagehandIcon,
StripeIcon,
@@ -117,10 +121,12 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
stripe: StripeIcon,
stagehand_agent: StagehandIcon,
stagehand: StagehandIcon,
smtp: SmtpIcon,
slack: SlackIcon,
sharepoint: MicrosoftSharepointIcon,
serper: SerperIcon,
sentry: SentryIcon,
sendgrid: SendgridIcon,
salesforce: SalesforceIcon,
s3: S3Icon,
resend: ResendIcon,
@@ -146,8 +152,10 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
microsoft_excel: MicrosoftExcelIcon,
memory: BrainIcon,
mem0: Mem0Icon,
mailgun: MailgunIcon,
mailchimp: MailchimpIcon,
linkup: LinkupIcon,
linkedin: LinkedInIcon,
linear: LinearIcon,
knowledge: PackageSearchIcon,
jira: JiraIcon,

View File

@@ -0,0 +1,88 @@
---
title: LinkedIn
description: Share posts and manage your LinkedIn presence
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="linkedin"
color="#0072B1"
/>
{/* MANUAL-CONTENT-START:intro */}
[LinkedIn](https://www.linkedin.com) is the worlds largest professional networking platform, empowering users to build their careers, connect with their network, and share professional content. LinkedIn is widely used by professionals across industries for personal branding, recruiting, job search, and business development.
With LinkedIn, you can easily share posts to your personal feed to engage with your network, and access information about your profile to highlight your professional achievements. Automated integration with Sim allows you to leverage LinkedIn functionality programmatically—enabling agents and workflows to post updates, report on your professional presence, and keep your feed active without manual effort.
Key LinkedIn features available through this integration include:
- **Share Posts:** Automatically publish professional updates, articles, or announcements to your LinkedIn personal feed.
- **Profile Information:** Retrieve detailed information about your LinkedIn profile to monitor or use in downstream tasks within your workflows.
These capabilities make it easy to keep your LinkedIn network engaged and to extend your professional reach efficiently as part of your AI or workflow automation strategy.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate LinkedIn into workflows. Share posts to your personal feed and access your LinkedIn profile information.
## Tools
### `linkedin_share_post`
Share a post to your personal LinkedIn feed
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `text` | string | Yes | The text content of your LinkedIn post |
| `visibility` | string | No | Who can see this post: "PUBLIC" or "CONNECTIONS" \(default: "PUBLIC"\) |
| `request` | string | No | No description |
| `output` | string | No | No description |
| `output` | string | No | No description |
| `specificContent` | string | No | No description |
| `shareCommentary` | string | No | No description |
| `visibility` | string | No | No description |
| `headers` | string | No | No description |
| `output` | string | No | No description |
| `output` | string | No | No description |
| `output` | string | No | No description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `postId` | string | Created post ID |
| `profile` | json | LinkedIn profile information |
| `error` | string | Error message if operation failed |
### `linkedin_get_profile`
Retrieve your LinkedIn profile information
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `postId` | string | Created post ID |
| `profile` | json | LinkedIn profile information |
| `error` | string | Error message if operation failed |
## Notes
- Category: `tools`
- Type: `linkedin`

View File

@@ -0,0 +1,221 @@
---
title: Mailgun
description: Send emails and manage mailing lists with Mailgun
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="mailgun"
color="#F06248"
/>
{/* MANUAL-CONTENT-START:intro */}
[Mailgun](https://www.mailgun.com) is a powerful email delivery service designed for developers and businesses to send, receive, and track emails effortlessly. Mailgun enables you to leverage robust APIs for reliable transactional and marketing email, flexible mailing list management, and advanced event tracking.
With Mailgun's comprehensive feature set, you can automate key email operations and closely monitor deliverability and recipient engagement. This makes it an ideal solution for workflow automation where communications, notifications, and campaign mails are core parts of your processes.
Key features of Mailgun include:
- **Transactional Email Sending:** Deliver high-volume emails such as account notifications, receipts, alerts, and password resets.
- **Rich Email Content:** Send both plain text and HTML emails, and use tags for categorizing and tracking your messages.
- **Mailing List Management:** Create, update, and manage mailing lists and members to send grouped communications efficiently.
- **Domain Information:** Retrieve details about your sending domains to monitor configuration and health.
- **Event Tracking:** Analyze email deliverability and engagement with detailed event data on sent messages.
- **Message Retrieval:** Access stored messages for compliance, analysis, or troubleshooting needs.
By integrating Mailgun into Sim, your agents are empowered to programmatically send emails, manage email lists, access domain information, and monitor real-time events as part of automated workflows. This allows for intelligent, data-driven engagement with your users directly from your AI-powered processes.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Mailgun into your workflow. Send transactional emails, manage mailing lists and members, view domain information, and track email events. Supports text and HTML emails, tags for tracking, and comprehensive list management.
## Tools
### `mailgun_send_message`
Send an email using Mailgun API
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Mailgun API key |
| `domain` | string | Yes | Mailgun domain \(e.g., mg.example.com\) |
| `from` | string | Yes | Sender email address |
| `to` | string | Yes | Recipient email address \(comma-separated for multiple\) |
| `subject` | string | Yes | Email subject |
| `text` | string | No | Plain text body of the email |
| `html` | string | No | HTML body of the email |
| `cc` | string | No | CC email address \(comma-separated for multiple\) |
| `bcc` | string | No | BCC email address \(comma-separated for multiple\) |
| `tags` | string | No | Tags for the email \(comma-separated\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the message was sent successfully |
| `id` | string | Message ID |
| `message` | string | Response message from Mailgun |
### `mailgun_get_message`
Retrieve a stored message by its key
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Mailgun API key |
| `domain` | string | Yes | Mailgun domain |
| `messageKey` | string | Yes | Message storage key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the request was successful |
| `recipients` | string | Message recipients |
| `from` | string | Sender email |
| `subject` | string | Message subject |
| `bodyPlain` | string | Plain text body |
| `strippedText` | string | Stripped text |
| `strippedSignature` | string | Stripped signature |
| `bodyHtml` | string | HTML body |
| `strippedHtml` | string | Stripped HTML |
| `attachmentCount` | number | Number of attachments |
| `timestamp` | number | Message timestamp |
| `messageHeaders` | json | Message headers |
| `contentIdMap` | json | Content ID map |
### `mailgun_list_messages`
List events (logs) for messages sent through Mailgun
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Mailgun API key |
| `domain` | string | Yes | Mailgun domain |
| `event` | string | No | Filter by event type \(accepted, delivered, failed, opened, clicked, etc.\) |
| `limit` | number | No | Maximum number of events to return \(default: 100\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the request was successful |
| `items` | json | Array of event items |
| `paging` | json | Paging information |
### `mailgun_create_mailing_list`
Create a new mailing list
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Mailgun API key |
| `address` | string | Yes | Mailing list address \(e.g., list@example.com\) |
| `name` | string | No | Mailing list name |
| `description` | string | No | Mailing list description |
| `accessLevel` | string | No | Access level: readonly, members, or everyone |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the list was created successfully |
| `message` | string | Response message |
| `list` | json | Created mailing list details |
### `mailgun_get_mailing_list`
Get details of a mailing list
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Mailgun API key |
| `address` | string | Yes | Mailing list address |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the request was successful |
| `list` | json | Mailing list details |
### `mailgun_add_list_member`
Add a member to a mailing list
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Mailgun API key |
| `listAddress` | string | Yes | Mailing list address |
| `address` | string | Yes | Member email address |
| `name` | string | No | Member name |
| `vars` | string | No | JSON string of custom variables |
| `subscribed` | boolean | No | Whether the member is subscribed |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the member was added successfully |
| `message` | string | Response message |
| `member` | json | Added member details |
### `mailgun_list_domains`
List all domains for your Mailgun account
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Mailgun API key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the request was successful |
| `totalCount` | number | Total number of domains |
| `items` | json | Array of domain objects |
### `mailgun_get_domain`
Get details of a specific domain
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Mailgun API key |
| `domain` | string | Yes | Domain name |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the request was successful |
| `domain` | json | Domain details |
## Notes
- Category: `tools`
- Type: `mailgun`

View File

@@ -33,8 +33,10 @@
"jira",
"knowledge",
"linear",
"linkedin",
"linkup",
"mailchimp",
"mailgun",
"mem0",
"memory",
"microsoft_excel",
@@ -60,10 +62,12 @@
"resend",
"s3",
"salesforce",
"sendgrid",
"sentry",
"serper",
"sharepoint",
"slack",
"smtp",
"stagehand",
"stagehand_agent",
"stripe",

View File

@@ -0,0 +1,396 @@
---
title: SendGrid
description: Send emails and manage contacts, lists, and templates with SendGrid
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="sendgrid"
color="#1A82E2"
/>
{/* MANUAL-CONTENT-START:intro */}
[SendGrid](https://sendgrid.com) is a leading cloud-based email delivery platform trusted by developers and businesses to send reliable transactional and marketing emails at scale. With its robust APIs and powerful tools, SendGrid enables you to manage all aspects of your email communication, from sending notifications and receipts to managing complex marketing campaigns.
SendGrid empowers users with a full suite of email operations, allowing you to automate critical email workflows and closely manage contact lists, templates, and recipient engagement. Its seamless integration with Sim enables agents and workflows to deliver targeted messages, maintain dynamic contact and recipient lists, trigger personalized emails through templates, and track the results in real time.
Key features of SendGrid include:
- **Transactional Email:** Send automated and high-volume transactional emails (like notifications, receipts, and password resets).
- **Dynamic Templates:** Use rich HTML or text templates with dynamic data for highly personalized communication at scale.
- **Contact Management:** Add and update marketing contacts, manage recipient lists, and target segments for campaigns.
- **Attachments Support:** Include one or more file attachments in your emails.
- **Comprehensive API Coverage:** Programmatically manage emails, contacts, lists, templates, suppression groups, and more.
By connecting SendGrid with Sim, your agents can:
- Send both simple and advanced (templated or multi-recipient) emails as part of any workflow.
- Manage and segment contacts and lists automatically.
- Leverage templates for consistency and dynamic personalization.
- Track and respond to email engagement within your automated processes.
This integration allows you to automate all critical communication flows, ensure messages reach the right audience, and maintain control over your organizations email strategy, directly from Sim workflows.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate SendGrid into your workflow. Send transactional emails, manage marketing contacts and lists, and work with email templates. Supports dynamic templates, attachments, and comprehensive contact management.
## Tools
### `sendgrid_send_mail`
Send an email using SendGrid API
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | SendGrid API key |
| `from` | string | Yes | Sender email address \(must be verified in SendGrid\) |
| `fromName` | string | No | Sender name |
| `to` | string | Yes | Recipient email address |
| `toName` | string | No | Recipient name |
| `subject` | string | No | Email subject \(required unless using a template with pre-defined subject\) |
| `content` | string | No | Email body content \(required unless using a template with pre-defined content\) |
| `contentType` | string | No | Content type \(text/plain or text/html\) |
| `cc` | string | No | CC email address |
| `bcc` | string | No | BCC email address |
| `replyTo` | string | No | Reply-to email address |
| `replyToName` | string | No | Reply-to name |
| `attachments` | file[] | No | Files to attach to the email |
| `templateId` | string | No | SendGrid template ID to use |
| `dynamicTemplateData` | json | No | JSON object of dynamic template data |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the email was sent successfully |
| `messageId` | string | SendGrid message ID |
| `to` | string | Recipient email address |
| `subject` | string | Email subject |
### `sendgrid_add_contact`
Add a new contact to SendGrid
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | SendGrid API key |
| `email` | string | Yes | Contact email address |
| `firstName` | string | No | Contact first name |
| `lastName` | string | No | Contact last name |
| `customFields` | json | No | JSON object of custom field key-value pairs \(use field IDs like e1_T, e2_N, e3_D, not field names\) |
| `listIds` | string | No | Comma-separated list IDs to add the contact to |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `jobId` | string | Job ID for tracking the async contact creation |
| `email` | string | Contact email address |
| `firstName` | string | Contact first name |
| `lastName` | string | Contact last name |
| `message` | string | Status message |
### `sendgrid_get_contact`
Get a specific contact by ID from SendGrid
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | SendGrid API key |
| `contactId` | string | Yes | Contact ID |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Contact ID |
| `email` | string | Contact email address |
| `firstName` | string | Contact first name |
| `lastName` | string | Contact last name |
| `createdAt` | string | Creation timestamp |
| `updatedAt` | string | Last update timestamp |
| `listIds` | json | Array of list IDs the contact belongs to |
| `customFields` | json | Custom field values |
### `sendgrid_search_contacts`
Search for contacts in SendGrid using a query
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | SendGrid API key |
| `query` | string | Yes | Search query \(e.g., \"email LIKE '%example.com%' AND CONTAINS\(list_ids, 'list-id'\)\"\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `contacts` | json | Array of matching contacts |
| `contactCount` | number | Total number of contacts found |
### `sendgrid_delete_contacts`
Delete one or more contacts from SendGrid
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | SendGrid API key |
| `contactIds` | string | Yes | Comma-separated contact IDs to delete |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `jobId` | string | Job ID for the deletion request |
### `sendgrid_create_list`
Create a new contact list in SendGrid
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | SendGrid API key |
| `name` | string | Yes | List name |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | List ID |
| `name` | string | List name |
| `contactCount` | number | Number of contacts in the list |
### `sendgrid_get_list`
Get a specific list by ID from SendGrid
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | SendGrid API key |
| `listId` | string | Yes | List ID |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | List ID |
| `name` | string | List name |
| `contactCount` | number | Number of contacts in the list |
### `sendgrid_list_all_lists`
Get all contact lists from SendGrid
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | SendGrid API key |
| `pageSize` | number | No | Number of lists to return per page \(default: 100\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `lists` | json | Array of lists |
### `sendgrid_delete_list`
Delete a contact list from SendGrid
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | SendGrid API key |
| `listId` | string | Yes | List ID to delete |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `message` | string | Success message |
### `sendgrid_add_contacts_to_list`
Add or update contacts and assign them to a list in SendGrid (uses PUT /v3/marketing/contacts)
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | SendGrid API key |
| `listId` | string | Yes | List ID to add contacts to |
| `contacts` | json | Yes | JSON array of contact objects. Each contact must have at least: email \(or phone_number_id/external_id/anonymous_id\). Example: \[\{"email": "user@example.com", "first_name": "John"\}\] |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `jobId` | string | Job ID for tracking the async operation |
| `message` | string | Status message |
### `sendgrid_remove_contacts_from_list`
Remove contacts from a specific list in SendGrid
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | SendGrid API key |
| `listId` | string | Yes | List ID |
| `contactIds` | string | Yes | Comma-separated contact IDs to remove from the list |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `jobId` | string | Job ID for the request |
### `sendgrid_create_template`
Create a new email template in SendGrid
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | SendGrid API key |
| `name` | string | Yes | Template name |
| `generation` | string | No | Template generation type \(legacy or dynamic, default: dynamic\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Template ID |
| `name` | string | Template name |
| `generation` | string | Template generation |
| `updatedAt` | string | Last update timestamp |
| `versions` | json | Array of template versions |
### `sendgrid_get_template`
Get a specific template by ID from SendGrid
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | SendGrid API key |
| `templateId` | string | Yes | Template ID |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Template ID |
| `name` | string | Template name |
| `generation` | string | Template generation |
| `updatedAt` | string | Last update timestamp |
| `versions` | json | Array of template versions |
### `sendgrid_list_templates`
Get all email templates from SendGrid
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | SendGrid API key |
| `generations` | string | No | Filter by generation \(legacy, dynamic, or both\) |
| `pageSize` | number | No | Number of templates to return per page \(default: 20\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `templates` | json | Array of templates |
### `sendgrid_delete_template`
Delete an email template from SendGrid
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | SendGrid API key |
| `templateId` | string | Yes | Template ID to delete |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `messageId` | string | Email message ID \(send_mail\) |
| `id` | string | Resource ID |
| `jobId` | string | Job ID for async operations |
| `email` | string | Email address |
| `firstName` | string | First name |
| `lastName` | string | Last name |
| `contacts` | json | Array of contacts |
| `contactCount` | number | Number of contacts |
| `lists` | json | Array of lists |
| `templates` | json | Array of templates |
| `message` | string | Status or success message |
| `name` | string | Resource name |
| `generation` | string | Template generation |
### `sendgrid_create_template_version`
Create a new version of an email template in SendGrid
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | SendGrid API key |
| `templateId` | string | Yes | Template ID |
| `name` | string | Yes | Version name |
| `subject` | string | Yes | Email subject line |
| `htmlContent` | string | No | HTML content of the template |
| `plainContent` | string | No | Plain text content of the template |
| `active` | boolean | No | Whether this version is active \(default: true\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Version ID |
| `templateId` | string | Template ID |
| `name` | string | Version name |
| `subject` | string | Email subject |
| `active` | boolean | Whether this version is active |
| `htmlContent` | string | HTML content |
| `plainContent` | string | Plain text content |
| `updatedAt` | string | Last update timestamp |
## Notes
- Category: `tools`
- Type: `sendgrid`

View File

@@ -0,0 +1,78 @@
---
title: SMTP
description: Send emails via any SMTP mail server
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="smtp"
color="#4A5568"
/>
{/* MANUAL-CONTENT-START:intro */}
[SMTP (Simple Mail Transfer Protocol)](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol) is the foundational standard for email transmission across the Internet. By connecting to any SMTP-compatible server—such as Gmail, Outlook, or your organization's own mail infrastructure—you can send emails programmatically and automate your outbound communications.
SMTP integration allows you to fully customize email sending through direct server connectivity, supporting both basic and advanced email use cases. With SMTP, you can control every aspect of message delivery, recipient management, and content formatting, making it suitable for transactional notifications, bulk mailings, and any automated workflow requiring robust outbound email delivery.
**Key features available via SMTP integration include:**
- **Universal Email Delivery:** Send emails using any SMTP server by configuring standard server connection parameters.
- **Customizable Sender and Recipients:** Specify sender address, display name, primary recipients, as well as CC and BCC fields.
- **Rich Content Support:** Send plain text or richly formatted HTML emails according to your requirements.
- **Attachments:** Include multiple files as attachments in outgoing emails.
- **Flexible Security:** Connect using TLS, SSL, or standard (unencrypted) protocols as supported by your SMTP provider.
- **Advanced Headers:** Set reply-to headers and other advanced email options to cater for complex mailflows and user interactions.
By integrating SMTP with Sim, agents and workflows can programmatically send emails as part of any automated process—ranging from sending notifications and confirmations, to automating external communications, reporting, and document delivery. This offers a highly flexible, provider-agnostic approach to managing email directly within your AI-driven processes.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Send emails using any SMTP server (Gmail, Outlook, custom servers, etc.). Configure SMTP connection settings and send emails with full control over content, recipients, and attachments.
## Tools
### `smtp_send_mail`
Send emails via SMTP server
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `smtpHost` | string | Yes | SMTP server hostname \(e.g., smtp.gmail.com\) |
| `smtpPort` | number | Yes | SMTP server port \(587 for TLS, 465 for SSL\) |
| `smtpUsername` | string | Yes | SMTP authentication username |
| `smtpPassword` | string | Yes | SMTP authentication password |
| `smtpSecure` | string | Yes | Security protocol \(TLS, SSL, or None\) |
| `from` | string | Yes | Sender email address |
| `to` | string | Yes | Recipient email address |
| `subject` | string | Yes | Email subject |
| `body` | string | Yes | Email body content |
| `contentType` | string | No | Content type \(text or html\) |
| `fromName` | string | No | Display name for sender |
| `cc` | string | No | CC recipients \(comma-separated\) |
| `bcc` | string | No | BCC recipients \(comma-separated\) |
| `replyTo` | string | No | Reply-to email address |
| `attachments` | file[] | No | Files to attach to the email |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the email was sent successfully |
| `messageId` | string | Message ID from SMTP server |
| `to` | string | Recipient email address |
| `subject` | string | Email subject |
| `error` | string | Error message if sending failed |
## Notes
- Category: `tools`
- Type: `smtp`

View File

@@ -6,6 +6,23 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { createMockRequest } from '@/app/api/__test-utils__/utils'
const createMockStream = () => {
return new ReadableStream({
start(controller) {
controller.enqueue(
new TextEncoder().encode('data: {"blockId":"agent-1","chunk":"Hello"}\n\n')
)
controller.enqueue(
new TextEncoder().encode('data: {"blockId":"agent-1","chunk":" world"}\n\n')
)
controller.enqueue(
new TextEncoder().encode('data: {"event":"final","data":{"success":true}}\n\n')
)
controller.close()
},
})
}
vi.mock('@/lib/execution/preprocessing', () => ({
preprocessExecution: vi.fn().mockResolvedValue({
success: true,
@@ -36,28 +53,37 @@ vi.mock('@/lib/logs/execution/logging-session', () => ({
})),
}))
describe('Chat Identifier API Route', () => {
const createMockStream = () => {
return new ReadableStream({
start(controller) {
controller.enqueue(
new TextEncoder().encode('data: {"blockId":"agent-1","chunk":"Hello"}\n\n')
)
controller.enqueue(
new TextEncoder().encode('data: {"blockId":"agent-1","chunk":" world"}\n\n')
)
controller.enqueue(
new TextEncoder().encode('data: {"event":"final","data":{"success":true}}\n\n')
)
controller.close()
},
})
}
vi.mock('@/lib/workflows/streaming', () => ({
createStreamingResponse: vi.fn().mockImplementation(async () => createMockStream()),
}))
vi.mock('@/lib/utils', () => ({
SSE_HEADERS: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
'X-Accel-Buffering': 'no',
},
generateRequestId: vi.fn().mockReturnValue('test-request-id'),
}))
vi.mock('@/app/api/workflows/[id]/execute/route', () => ({
createFilteredResult: vi.fn().mockImplementation((result: any) => ({
...result,
logs: undefined,
metadata: result.metadata
? {
...result.metadata,
workflowConnections: undefined,
}
: undefined,
})),
}))
describe('Chat Identifier API Route', () => {
const mockAddCorsHeaders = vi.fn().mockImplementation((response) => response)
const mockValidateChatAuth = vi.fn().mockResolvedValue({ authorized: true })
const mockSetChatAuthCookie = vi.fn()
const mockCreateStreamingResponse = vi.fn().mockResolvedValue(createMockStream())
const mockChatResult = [
{
@@ -104,16 +130,6 @@ describe('Chat Identifier API Route', () => {
validateAuthToken: vi.fn().mockReturnValue(true),
}))
vi.doMock('@/lib/workflows/streaming', () => ({
createStreamingResponse: mockCreateStreamingResponse,
SSE_HEADERS: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
'X-Accel-Buffering': 'no',
},
}))
vi.doMock('@/lib/logs/console/logger', () => ({
createLogger: vi.fn().mockReturnValue({
debug: vi.fn(),
@@ -372,6 +388,7 @@ describe('Chat Identifier API Route', () => {
const params = Promise.resolve({ identifier: 'test-chat' })
const { POST } = await import('@/app/api/chat/[identifier]/route')
const { createStreamingResponse } = await import('@/lib/workflows/streaming')
const response = await POST(req, { params })
@@ -380,7 +397,7 @@ describe('Chat Identifier API Route', () => {
expect(response.headers.get('Cache-Control')).toBe('no-cache')
expect(response.headers.get('Connection')).toBe('keep-alive')
expect(mockCreateStreamingResponse).toHaveBeenCalledWith(
expect(createStreamingResponse).toHaveBeenCalledWith(
expect.objectContaining({
workflow: expect.objectContaining({
id: 'workflow-id',
@@ -396,7 +413,7 @@ describe('Chat Identifier API Route', () => {
}),
})
)
})
}, 10000)
it('should handle streaming response body correctly', async () => {
const req = createMockRequest('POST', { input: 'Hello world' })
@@ -423,8 +440,9 @@ describe('Chat Identifier API Route', () => {
})
it('should handle workflow execution errors gracefully', async () => {
const originalStreamingResponse = mockCreateStreamingResponse.getMockImplementation()
mockCreateStreamingResponse.mockImplementationOnce(async () => {
const { createStreamingResponse } = await import('@/lib/workflows/streaming')
const originalStreamingResponse = vi.mocked(createStreamingResponse).getMockImplementation()
vi.mocked(createStreamingResponse).mockImplementationOnce(async () => {
throw new Error('Execution failed')
})
@@ -442,7 +460,7 @@ describe('Chat Identifier API Route', () => {
expect(data).toHaveProperty('message', 'Execution failed')
if (originalStreamingResponse) {
mockCreateStreamingResponse.mockImplementation(originalStreamingResponse)
vi.mocked(createStreamingResponse).mockImplementation(originalStreamingResponse)
}
})
@@ -474,10 +492,11 @@ describe('Chat Identifier API Route', () => {
const params = Promise.resolve({ identifier: 'test-chat' })
const { POST } = await import('@/app/api/chat/[identifier]/route')
const { createStreamingResponse } = await import('@/lib/workflows/streaming')
await POST(req, { params })
expect(mockCreateStreamingResponse).toHaveBeenCalledWith(
expect(createStreamingResponse).toHaveBeenCalledWith(
expect.objectContaining({
input: expect.objectContaining({
input: 'Hello world',
@@ -492,10 +511,11 @@ describe('Chat Identifier API Route', () => {
const params = Promise.resolve({ identifier: 'test-chat' })
const { POST } = await import('@/app/api/chat/[identifier]/route')
const { createStreamingResponse } = await import('@/lib/workflows/streaming')
await POST(req, { params })
expect(mockCreateStreamingResponse).toHaveBeenCalledWith(
expect(createStreamingResponse).toHaveBeenCalledWith(
expect.objectContaining({
input: expect.objectContaining({
input: 'Hello world',

View File

@@ -0,0 +1,227 @@
import { type NextRequest, NextResponse } from 'next/server'
import nodemailer from 'nodemailer'
import { z } from 'zod'
import { checkHybridAuth } from '@/lib/auth/hybrid'
import { createLogger } from '@/lib/logs/console/logger'
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
import { generateRequestId } from '@/lib/utils'
export const dynamic = 'force-dynamic'
const logger = createLogger('SmtpSendAPI')
const SmtpSendSchema = z.object({
smtpHost: z.string().min(1, 'SMTP host is required'),
smtpPort: z.number().min(1).max(65535, 'Port must be between 1 and 65535'),
smtpUsername: z.string().min(1, 'SMTP username is required'),
smtpPassword: z.string().min(1, 'SMTP password is required'),
smtpSecure: z.enum(['TLS', 'SSL', 'None']),
from: z.string().email('Invalid from email address').min(1, 'From address is required'),
to: z.string().min(1, 'To email is required'),
subject: z.string().min(1, 'Subject is required'),
body: z.string().min(1, 'Email body is required'),
contentType: z.enum(['text', 'html']).optional().nullable(),
fromName: z.string().optional().nullable(),
cc: z.string().optional().nullable(),
bcc: z.string().optional().nullable(),
replyTo: z.string().optional().nullable(),
attachments: z.array(z.any()).optional().nullable(),
})
export async function POST(request: NextRequest) {
const requestId = generateRequestId()
try {
const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized SMTP send attempt: ${authResult.error}`)
return NextResponse.json(
{
success: false,
error: authResult.error || 'Authentication required',
},
{ status: 401 }
)
}
logger.info(`[${requestId}] Authenticated SMTP request via ${authResult.authType}`, {
userId: authResult.userId,
})
const body = await request.json()
const validatedData = SmtpSendSchema.parse(body)
logger.info(`[${requestId}] Sending email via SMTP`, {
host: validatedData.smtpHost,
port: validatedData.smtpPort,
to: validatedData.to,
subject: validatedData.subject,
secure: validatedData.smtpSecure,
})
const transporter = nodemailer.createTransport({
host: validatedData.smtpHost,
port: validatedData.smtpPort,
secure: validatedData.smtpSecure === 'SSL',
auth: {
user: validatedData.smtpUsername,
pass: validatedData.smtpPassword,
},
tls:
validatedData.smtpSecure === 'None'
? {
rejectUnauthorized: false,
}
: {
rejectUnauthorized: true,
},
})
const contentType = validatedData.contentType || 'text'
const fromAddress = validatedData.fromName
? `"${validatedData.fromName}" <${validatedData.from}>`
: validatedData.from
const mailOptions: nodemailer.SendMailOptions = {
from: fromAddress,
to: validatedData.to,
subject: validatedData.subject,
[contentType === 'html' ? 'html' : 'text']: validatedData.body,
}
if (validatedData.cc) {
mailOptions.cc = validatedData.cc
}
if (validatedData.bcc) {
mailOptions.bcc = validatedData.bcc
}
if (validatedData.replyTo) {
mailOptions.replyTo = validatedData.replyTo
}
if (validatedData.attachments && validatedData.attachments.length > 0) {
const rawAttachments = validatedData.attachments
logger.info(`[${requestId}] Processing ${rawAttachments.length} attachment(s)`)
const attachments = processFilesToUserFiles(rawAttachments, requestId, logger)
if (attachments.length > 0) {
const totalSize = attachments.reduce((sum, file) => sum + file.size, 0)
const maxSize = 25 * 1024 * 1024
if (totalSize > maxSize) {
const sizeMB = (totalSize / (1024 * 1024)).toFixed(2)
return NextResponse.json(
{
success: false,
error: `Total attachment size (${sizeMB}MB) exceeds SMTP limit of 25MB`,
},
{ status: 400 }
)
}
const attachmentBuffers = await Promise.all(
attachments.map(async (file) => {
try {
logger.info(
`[${requestId}] Downloading attachment: ${file.name} (${file.size} bytes)`
)
const buffer = await downloadFileFromStorage(file, requestId, logger)
return {
filename: file.name,
content: buffer,
contentType: file.type || 'application/octet-stream',
}
} catch (error) {
logger.error(`[${requestId}] Failed to download attachment ${file.name}:`, error)
throw new Error(
`Failed to download attachment "${file.name}": ${error instanceof Error ? error.message : 'Unknown error'}`
)
}
})
)
logger.info(`[${requestId}] Processed ${attachmentBuffers.length} attachment(s)`)
mailOptions.attachments = attachmentBuffers
}
}
const result = await transporter.sendMail(mailOptions)
logger.info(`[${requestId}] Email sent successfully via SMTP`, {
messageId: result.messageId,
to: validatedData.to,
})
return NextResponse.json({
success: true,
messageId: result.messageId,
to: validatedData.to,
subject: validatedData.subject,
})
} catch (error: unknown) {
if (error instanceof z.ZodError) {
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
return NextResponse.json(
{
success: false,
error: 'Invalid request data',
details: error.errors,
},
{ status: 400 }
)
}
// Type guard for error objects with code property
const isNodeError = (err: unknown): err is NodeJS.ErrnoException => {
return err instanceof Error && 'code' in err
}
let errorMessage = 'Failed to send email via SMTP'
if (isNodeError(error)) {
if (error.code === 'EAUTH') {
errorMessage = 'SMTP authentication failed - check username and password'
} else if (error.code === 'ECONNECTION' || error.code === 'ECONNREFUSED') {
errorMessage = 'Could not connect to SMTP server - check host and port'
} else if (error.code === 'ECONNRESET') {
errorMessage = 'Connection was reset by SMTP server'
} else if (error.code === 'ETIMEDOUT') {
errorMessage = 'SMTP server connection timeout'
}
}
// Check for SMTP response codes
const hasResponseCode = (err: unknown): err is { responseCode: number } => {
return typeof err === 'object' && err !== null && 'responseCode' in err
}
if (hasResponseCode(error)) {
if (error.responseCode >= 500) {
errorMessage = 'SMTP server error - please try again later'
} else if (error.responseCode >= 400) {
errorMessage = 'Email rejected by SMTP server - check recipient addresses'
}
}
logger.error(`[${requestId}] Error sending email via SMTP:`, {
error: error instanceof Error ? error.message : String(error),
code: isNodeError(error) ? error.code : undefined,
responseCode: hasResponseCode(error) ? error.responseCode : undefined,
})
return NextResponse.json(
{
success: false,
error: errorMessage,
},
{ status: 500 }
)
}
}

View File

@@ -231,6 +231,7 @@ const SCOPE_DESCRIPTIONS: Record<string, string> = {
'projects:full': 'Full access to manage your Pipedrive projects',
'webhooks:read': 'Read your Pipedrive webhooks',
'webhooks:full': 'Full access to manage your Pipedrive webhooks',
w_member_social: 'Access your LinkedIn profile',
}
function getScopeDescription(scope: string): string {

View File

@@ -411,7 +411,7 @@ export function Panel() {
</PopoverItem> */}
<PopoverItem
onClick={handleExportJson}
disabled={isExporting || !currentWorkflow}
disabled={!userPermissions.canEdit || isExporting || !currentWorkflow}
>
<Braces className='h-3 w-3' />
<span>Export workflow</span>

View File

@@ -4,6 +4,7 @@ import { useCallback, useState } from 'react'
import clsx from 'clsx'
import { ChevronRight, Folder, FolderOpen } from 'lucide-react'
import { useParams, useRouter } from 'next/navigation'
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
import { ContextMenu } from '@/app/workspace/[workspaceId]/w/components/sidebar/components-new/workflow-list/components/context-menu/context-menu'
import { DeleteModal } from '@/app/workspace/[workspaceId]/w/components/sidebar/components-new/workflow-list/components/delete-modal/delete-modal'
import {
@@ -40,6 +41,7 @@ export function FolderItem({ folder, level, hoverHandlers }: FolderItemProps) {
const workspaceId = params.workspaceId as string
const updateFolderMutation = useUpdateFolder()
const createWorkflowMutation = useCreateWorkflow()
const userPermissions = useUserPermissionsContext()
// Delete modal state
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false)
@@ -260,6 +262,9 @@ export function FolderItem({ folder, level, hoverHandlers }: FolderItemProps) {
onDuplicate={handleDuplicateFolder}
onDelete={() => setIsDeleteModalOpen(true)}
showCreate={true}
disableRename={!userPermissions.canEdit}
disableDuplicate={!userPermissions.canEdit}
disableDelete={!userPermissions.canEdit}
/>
{/* Delete Modal */}

View File

@@ -4,6 +4,7 @@ import { useCallback, useRef, useState } from 'react'
import clsx from 'clsx'
import Link from 'next/link'
import { useParams } from 'next/navigation'
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
import { ContextMenu } from '@/app/workspace/[workspaceId]/w/components/sidebar/components-new/workflow-list/components/context-menu/context-menu'
import { DeleteModal } from '@/app/workspace/[workspaceId]/w/components/sidebar/components-new/workflow-list/components/delete-modal/delete-modal'
import { Avatars } from '@/app/workspace/[workspaceId]/w/components/sidebar/components-new/workflow-list/components/workflow-item/avatars/avatars'
@@ -40,6 +41,7 @@ export function WorkflowItem({ workflow, active, level, onWorkflowClick }: Workf
const workspaceId = params.workspaceId as string
const { selectedWorkflows } = useFolderStore()
const { updateWorkflow, workflows } = useWorkflowRegistry()
const userPermissions = useUserPermissionsContext()
const isSelected = selectedWorkflows.has(workflow.id)
// Delete modal state
@@ -309,6 +311,10 @@ export function WorkflowItem({ workflow, active, level, onWorkflowClick }: Workf
showRename={selectedWorkflows.size <= 1}
showDuplicate={true}
showExport={true}
disableRename={!userPermissions.canEdit}
disableDuplicate={!userPermissions.canEdit}
disableExport={!userPermissions.canEdit}
disableDelete={!userPermissions.canEdit}
/>
{/* Delete Confirmation Modal */}

View File

@@ -0,0 +1,112 @@
import { LinkedInIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { LinkedInResponse } from '@/tools/linkedin/types'
export const LinkedInBlock: BlockConfig<LinkedInResponse> = {
type: 'linkedin',
name: 'LinkedIn',
description: 'Share posts and manage your LinkedIn presence',
authMode: AuthMode.OAuth,
longDescription:
'Integrate LinkedIn into workflows. Share posts to your personal feed and access your LinkedIn profile information.',
docsLink: 'https://docs.sim.ai/tools/linkedin',
category: 'tools',
bgColor: '#0072B1',
icon: LinkedInIcon,
subBlocks: [
// Operation selection
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Share Post', id: 'share_post' },
{ label: 'Get Profile', id: 'get_profile' },
],
value: () => 'share_post',
},
// LinkedIn OAuth Authentication
{
id: 'credential',
title: 'LinkedIn Account',
type: 'oauth-input',
provider: 'linkedin',
serviceId: 'linkedin',
requiredScopes: ['profile', 'openid', 'email', 'w_member_social'],
placeholder: 'Select LinkedIn account',
required: true,
},
// Share Post specific fields
{
id: 'text',
title: 'Post Text',
type: 'long-input',
placeholder: 'What do you want to share on LinkedIn?',
condition: {
field: 'operation',
value: 'share_post',
},
required: true,
},
{
id: 'visibility',
title: 'Visibility',
type: 'dropdown',
options: [
{ label: 'Public', id: 'PUBLIC' },
{ label: 'Connections Only', id: 'CONNECTIONS' },
],
condition: {
field: 'operation',
value: 'share_post',
},
value: () => 'PUBLIC',
required: true,
},
],
tools: {
access: ['linkedin_share_post', 'linkedin_get_profile'],
config: {
tool: (inputs) => {
const operation = inputs.operation || 'share_post'
if (operation === 'get_profile') {
return 'linkedin_get_profile'
}
return 'linkedin_share_post'
},
params: (inputs) => {
const operation = inputs.operation || 'share_post'
const { credential, ...rest } = inputs
if (operation === 'get_profile') {
return {
accessToken: credential,
}
}
return {
text: rest.text,
visibility: rest.visibility || 'PUBLIC',
accessToken: credential,
}
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
credential: { type: 'string', description: 'LinkedIn access token' },
text: { type: 'string', description: 'Post text content' },
visibility: { type: 'string', description: 'Post visibility (PUBLIC or CONNECTIONS)' },
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
postId: { type: 'string', description: 'Created post ID' },
profile: { type: 'json', description: 'LinkedIn profile information' },
error: { type: 'string', description: 'Error message if operation failed' },
},
}

View File

@@ -0,0 +1,293 @@
import { MailgunIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import type { SendMessageResult } from '@/tools/mailgun/types'
export const MailgunBlock: BlockConfig<SendMessageResult> = {
type: 'mailgun',
name: 'Mailgun',
description: 'Send emails and manage mailing lists with Mailgun',
longDescription:
'Integrate Mailgun into your workflow. Send transactional emails, manage mailing lists and members, view domain information, and track email events. Supports text and HTML emails, tags for tracking, and comprehensive list management.',
docsLink: 'https://docs.sim.ai/tools/mailgun',
category: 'tools',
bgColor: '#F06248',
icon: MailgunIcon,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
// Message Operations
{ label: 'Send Message', id: 'send_message' },
{ label: 'Get Message', id: 'get_message' },
{ label: 'List Messages', id: 'list_messages' },
// Mailing List Operations
{ label: 'Create Mailing List', id: 'create_mailing_list' },
{ label: 'Get Mailing List', id: 'get_mailing_list' },
{ label: 'Add List Member', id: 'add_list_member' },
// Domain Operations
{ label: 'List Domains', id: 'list_domains' },
{ label: 'Get Domain', id: 'get_domain' },
],
value: () => 'send_message',
},
{
id: 'apiKey',
title: 'Mailgun API Key',
type: 'short-input',
password: true,
placeholder: 'Enter your Mailgun API key',
required: true,
},
{
id: 'domain',
title: 'Domain',
type: 'short-input',
placeholder: 'mg.example.com',
condition: {
field: 'operation',
value: ['send_message', 'get_message', 'list_messages', 'get_domain'],
},
required: true,
},
// Send Message fields
{
id: 'from',
title: 'From Email',
type: 'short-input',
placeholder: 'sender@example.com',
condition: { field: 'operation', value: 'send_message' },
required: true,
},
{
id: 'to',
title: 'To Email',
type: 'short-input',
placeholder: 'recipient@example.com',
condition: { field: 'operation', value: 'send_message' },
required: true,
},
{
id: 'subject',
title: 'Subject',
type: 'short-input',
placeholder: 'Email subject',
condition: { field: 'operation', value: 'send_message' },
required: true,
},
{
id: 'text',
title: 'Text Body',
type: 'long-input',
placeholder: 'Plain text email body',
condition: { field: 'operation', value: 'send_message' },
},
{
id: 'html',
title: 'HTML Body',
type: 'code',
placeholder: '<html><body>HTML email body</body></html>',
condition: { field: 'operation', value: 'send_message' },
},
{
id: 'cc',
title: 'CC',
type: 'short-input',
placeholder: 'cc@example.com',
condition: { field: 'operation', value: 'send_message' },
},
{
id: 'bcc',
title: 'BCC',
type: 'short-input',
placeholder: 'bcc@example.com',
condition: { field: 'operation', value: 'send_message' },
},
{
id: 'tags',
title: 'Tags',
type: 'short-input',
placeholder: 'tag1, tag2',
condition: { field: 'operation', value: 'send_message' },
},
// Get Message fields
{
id: 'messageKey',
title: 'Message Key',
type: 'short-input',
placeholder: 'Message storage key',
condition: { field: 'operation', value: 'get_message' },
required: true,
},
// List Messages fields
{
id: 'event',
title: 'Event Type',
type: 'dropdown',
options: [
{ label: 'All Events', id: '' },
{ label: 'Accepted', id: 'accepted' },
{ label: 'Delivered', id: 'delivered' },
{ label: 'Failed', id: 'failed' },
{ label: 'Opened', id: 'opened' },
{ label: 'Clicked', id: 'clicked' },
{ label: 'Unsubscribed', id: 'unsubscribed' },
{ label: 'Complained', id: 'complained' },
{ label: 'Stored', id: 'stored' },
],
value: () => '',
condition: { field: 'operation', value: 'list_messages' },
},
{
id: 'limit',
title: 'Limit',
type: 'short-input',
placeholder: '100',
condition: { field: 'operation', value: 'list_messages' },
},
// Create Mailing List fields
{
id: 'address',
title: 'List Address',
type: 'short-input',
placeholder: 'list@example.com',
condition: {
field: 'operation',
value: ['create_mailing_list', 'get_mailing_list', 'add_list_member'],
},
required: true,
},
{
id: 'name',
title: 'List Name',
type: 'short-input',
placeholder: 'My Mailing List',
condition: { field: 'operation', value: 'create_mailing_list' },
},
{
id: 'description',
title: 'Description',
type: 'long-input',
placeholder: 'Description of the mailing list',
condition: { field: 'operation', value: 'create_mailing_list' },
},
{
id: 'accessLevel',
title: 'Access Level',
type: 'dropdown',
options: [
{ label: 'Read Only', id: 'readonly' },
{ label: 'Members', id: 'members' },
{ label: 'Everyone', id: 'everyone' },
],
value: () => 'readonly',
condition: { field: 'operation', value: 'create_mailing_list' },
},
// Add List Member fields (reuse address from above for listAddress)
{
id: 'memberAddress',
title: 'Member Email',
type: 'short-input',
placeholder: 'member@example.com',
condition: { field: 'operation', value: 'add_list_member' },
required: true,
},
{
id: 'memberName',
title: 'Member Name',
type: 'short-input',
placeholder: 'John Doe',
condition: { field: 'operation', value: 'add_list_member' },
},
{
id: 'vars',
title: 'Custom Variables',
type: 'code',
placeholder: '{"key": "value"}',
condition: { field: 'operation', value: 'add_list_member' },
},
{
id: 'subscribed',
title: 'Subscribed',
type: 'dropdown',
options: [
{ label: 'Yes', id: 'true' },
{ label: 'No', id: 'false' },
],
value: () => 'true',
condition: { field: 'operation', value: 'add_list_member' },
},
],
tools: {
access: [
'mailgun_send_message',
'mailgun_get_message',
'mailgun_list_messages',
'mailgun_create_mailing_list',
'mailgun_get_mailing_list',
'mailgun_add_list_member',
'mailgun_list_domains',
'mailgun_get_domain',
],
config: {
tool: (params) => `mailgun_${params.operation}`,
params: (params) => {
const { operation, memberAddress, memberName, ...rest } = params
// Handle special field mappings for add_list_member
if (operation === 'add_list_member') {
return {
...rest,
listAddress: params.address,
address: memberAddress,
name: memberName,
subscribed: params.subscribed === 'true',
}
}
return rest
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
apiKey: { type: 'string', description: 'Mailgun API key' },
domain: { type: 'string', description: 'Mailgun domain' },
// Message inputs
from: { type: 'string', description: 'Sender email address' },
to: { type: 'string', description: 'Recipient email address' },
subject: { type: 'string', description: 'Email subject' },
text: { type: 'string', description: 'Plain text body' },
html: { type: 'string', description: 'HTML body' },
cc: { type: 'string', description: 'CC email address' },
bcc: { type: 'string', description: 'BCC email address' },
tags: { type: 'string', description: 'Tags for the email' },
messageKey: { type: 'string', description: 'Message storage key' },
event: { type: 'string', description: 'Event type filter' },
limit: { type: 'number', description: 'Number of events to return' },
// Mailing list inputs
address: { type: 'string', description: 'Mailing list address' },
name: { type: 'string', description: 'List or member name' },
description: { type: 'string', description: 'List description' },
accessLevel: { type: 'string', description: 'List access level' },
memberAddress: { type: 'string', description: 'Member email address' },
memberName: { type: 'string', description: 'Member name' },
vars: { type: 'string', description: 'Custom variables JSON' },
subscribed: { type: 'string', description: 'Member subscription status' },
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
id: { type: 'string', description: 'Message ID' },
message: { type: 'string', description: 'Response message' },
items: { type: 'json', description: 'Array of items (messages, domains)' },
list: { type: 'json', description: 'Mailing list details' },
member: { type: 'json', description: 'Member details' },
domain: { type: 'json', description: 'Domain details' },
totalCount: { type: 'number', description: 'Total count of items' },
},
}

View File

@@ -0,0 +1,478 @@
import { SendgridIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import type { SendMailResult } from '@/tools/sendgrid/types'
export const SendGridBlock: BlockConfig<SendMailResult> = {
type: 'sendgrid',
name: 'SendGrid',
description: 'Send emails and manage contacts, lists, and templates with SendGrid',
longDescription:
'Integrate SendGrid into your workflow. Send transactional emails, manage marketing contacts and lists, and work with email templates. Supports dynamic templates, attachments, and comprehensive contact management.',
docsLink: 'https://docs.sim.ai/tools/sendgrid',
category: 'tools',
bgColor: '#1A82E2',
icon: SendgridIcon,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
// Mail Operations
{ label: 'Send Mail', id: 'send_mail' },
// Contact Operations
{ label: 'Add Contact', id: 'add_contact' },
{ label: 'Get Contact', id: 'get_contact' },
{ label: 'Search Contacts', id: 'search_contacts' },
{ label: 'Delete Contacts', id: 'delete_contacts' },
// List Operations
{ label: 'Create List', id: 'create_list' },
{ label: 'Get List', id: 'get_list' },
{ label: 'List All Lists', id: 'list_all_lists' },
{ label: 'Delete List', id: 'delete_list' },
{ label: 'Add Contacts to List', id: 'add_contacts_to_list' },
{ label: 'Remove Contacts from List', id: 'remove_contacts_from_list' },
// Template Operations
{ label: 'Create Template', id: 'create_template' },
{ label: 'Get Template', id: 'get_template' },
{ label: 'List Templates', id: 'list_templates' },
{ label: 'Delete Template', id: 'delete_template' },
{ label: 'Create Template Version', id: 'create_template_version' },
],
value: () => 'send_mail',
},
{
id: 'apiKey',
title: 'SendGrid API Key',
type: 'short-input',
password: true,
placeholder: 'Enter your SendGrid API key',
required: true,
},
// Send Mail fields
{
id: 'from',
title: 'From Email',
type: 'short-input',
placeholder: 'sender@yourdomain.com',
condition: { field: 'operation', value: 'send_mail' },
required: true,
},
{
id: 'fromName',
title: 'From Name',
type: 'short-input',
placeholder: 'Sender Name',
condition: { field: 'operation', value: 'send_mail' },
},
{
id: 'to',
title: 'To Email',
type: 'short-input',
placeholder: 'recipient@example.com',
condition: { field: 'operation', value: 'send_mail' },
required: true,
},
{
id: 'toName',
title: 'To Name',
type: 'short-input',
placeholder: 'Recipient Name',
condition: { field: 'operation', value: 'send_mail' },
},
{
id: 'mailSubject',
title: 'Subject',
type: 'short-input',
placeholder: 'Email subject (required unless using template)',
condition: { field: 'operation', value: 'send_mail' },
},
{
id: 'content',
title: 'Content',
type: 'long-input',
placeholder: 'Email body content (required unless using template)',
condition: { field: 'operation', value: 'send_mail' },
},
{
id: 'contentType',
title: 'Content Type',
type: 'dropdown',
options: [
{ label: 'Plain Text', id: 'text/plain' },
{ label: 'HTML', id: 'text/html' },
],
value: () => 'text/plain',
condition: { field: 'operation', value: 'send_mail' },
},
{
id: 'cc',
title: 'CC',
type: 'short-input',
placeholder: 'cc@example.com',
condition: { field: 'operation', value: 'send_mail' },
},
{
id: 'bcc',
title: 'BCC',
type: 'short-input',
placeholder: 'bcc@example.com',
condition: { field: 'operation', value: 'send_mail' },
},
{
id: 'replyTo',
title: 'Reply To',
type: 'short-input',
placeholder: 'replyto@example.com',
condition: { field: 'operation', value: 'send_mail' },
},
{
id: 'replyToName',
title: 'Reply To Name',
type: 'short-input',
placeholder: 'Reply To Name',
condition: { field: 'operation', value: 'send_mail' },
},
{
id: 'mailTemplateId',
title: 'Template ID',
type: 'short-input',
placeholder: 'SendGrid template ID',
condition: { field: 'operation', value: 'send_mail' },
},
{
id: 'dynamicTemplateData',
title: 'Dynamic Template Data',
type: 'code',
placeholder: '{"name": "John", "order_id": "12345"}',
condition: { field: 'operation', value: 'send_mail' },
},
// File upload (basic mode)
{
id: 'attachmentFiles',
title: 'Attachments',
type: 'file-upload',
canonicalParamId: 'attachments',
placeholder: 'Upload files to attach',
condition: { field: 'operation', value: 'send_mail' },
mode: 'basic',
multiple: true,
required: false,
},
// Variable reference (advanced mode)
{
id: 'attachments',
title: 'Attachments',
type: 'short-input',
canonicalParamId: 'attachments',
placeholder: 'Reference files from previous blocks',
condition: { field: 'operation', value: 'send_mail' },
mode: 'advanced',
required: false,
},
// Contact fields
{
id: 'email',
title: 'Email',
type: 'short-input',
placeholder: 'contact@example.com',
condition: { field: 'operation', value: ['add_contact'] },
required: true,
},
{
id: 'firstName',
title: 'First Name',
type: 'short-input',
placeholder: 'John',
condition: { field: 'operation', value: ['add_contact'] },
},
{
id: 'lastName',
title: 'Last Name',
type: 'short-input',
placeholder: 'Doe',
condition: { field: 'operation', value: ['add_contact'] },
},
{
id: 'customFields',
title: 'Custom Fields',
type: 'code',
placeholder: '{"custom_field_1": "value1"}',
condition: { field: 'operation', value: ['add_contact'] },
},
{
id: 'contactListIds',
title: 'List IDs',
type: 'short-input',
placeholder: 'Comma-separated list IDs',
condition: { field: 'operation', value: ['add_contact'] },
},
{
id: 'contactId',
title: 'Contact ID',
type: 'short-input',
placeholder: 'Contact ID',
condition: { field: 'operation', value: ['get_contact'] },
required: true,
},
{
id: 'query',
title: 'Search Query',
type: 'long-input',
placeholder: "email LIKE '%example.com%'",
condition: { field: 'operation', value: ['search_contacts'] },
required: true,
},
{
id: 'contactIds',
title: 'Contact IDs',
type: 'short-input',
placeholder: 'Comma-separated contact IDs',
condition: {
field: 'operation',
value: ['delete_contacts', 'remove_contacts_from_list'],
},
required: true,
},
{
id: 'contacts',
title: 'Contacts (JSON Array)',
type: 'code',
placeholder: '[{"email": "user@example.com", "first_name": "John"}]',
condition: { field: 'operation', value: 'add_contacts_to_list' },
required: true,
},
// List fields
{
id: 'listName',
title: 'List Name',
type: 'short-input',
placeholder: 'List name',
condition: { field: 'operation', value: ['create_list'] },
required: true,
},
{
id: 'listId',
title: 'List ID',
type: 'short-input',
placeholder: 'List ID',
condition: {
field: 'operation',
value: ['get_list', 'delete_list', 'add_contacts_to_list', 'remove_contacts_from_list'],
},
required: true,
},
{
id: 'listPageSize',
title: 'Page Size',
type: 'short-input',
placeholder: '100',
condition: { field: 'operation', value: 'list_all_lists' },
},
// Template fields
{
id: 'templateName',
title: 'Template Name',
type: 'short-input',
placeholder: 'Template name',
condition: { field: 'operation', value: ['create_template'] },
required: true,
},
{
id: 'templateId',
title: 'Template ID',
type: 'short-input',
placeholder: 'Template ID',
condition: {
field: 'operation',
value: ['get_template', 'delete_template', 'create_template_version'],
},
required: true,
},
{
id: 'generation',
title: 'Template Generation',
type: 'dropdown',
options: [
{ label: 'Dynamic', id: 'dynamic' },
{ label: 'Legacy', id: 'legacy' },
],
value: () => 'dynamic',
condition: { field: 'operation', value: 'create_template' },
},
{
id: 'templateGenerations',
title: 'Filter by Generation',
type: 'short-input',
placeholder: 'legacy, dynamic, or both',
condition: { field: 'operation', value: 'list_templates' },
},
{
id: 'templatePageSize',
title: 'Page Size',
type: 'short-input',
placeholder: '20',
condition: { field: 'operation', value: 'list_templates' },
},
{
id: 'versionName',
title: 'Version Name',
type: 'short-input',
placeholder: 'Version name',
condition: { field: 'operation', value: 'create_template_version' },
required: true,
},
{
id: 'templateSubject',
title: 'Template Subject',
type: 'short-input',
placeholder: 'Email subject',
condition: { field: 'operation', value: 'create_template_version' },
required: true,
},
{
id: 'htmlContent',
title: 'HTML Content',
type: 'code',
placeholder: '<html><body>{{name}}</body></html>',
condition: { field: 'operation', value: 'create_template_version' },
},
{
id: 'plainContent',
title: 'Plain Text Content',
type: 'long-input',
placeholder: 'Plain text content',
condition: { field: 'operation', value: 'create_template_version' },
},
{
id: 'active',
title: 'Active',
type: 'dropdown',
options: [
{ label: 'Yes', id: 'true' },
{ label: 'No', id: 'false' },
],
value: () => 'true',
condition: { field: 'operation', value: 'create_template_version' },
},
],
tools: {
access: [
'sendgrid_send_mail',
'sendgrid_add_contact',
'sendgrid_get_contact',
'sendgrid_search_contacts',
'sendgrid_delete_contacts',
'sendgrid_create_list',
'sendgrid_get_list',
'sendgrid_list_all_lists',
'sendgrid_delete_list',
'sendgrid_add_contacts_to_list',
'sendgrid_remove_contacts_from_list',
'sendgrid_create_template',
'sendgrid_get_template',
'sendgrid_list_templates',
'sendgrid_delete_template',
'sendgrid_create_template_version',
],
config: {
tool: (params) => `sendgrid_${params.operation}`,
params: (params) => {
const {
operation,
mailSubject,
mailTemplateId,
listName,
templateName,
versionName,
templateSubject,
contactListIds,
templateGenerations,
listPageSize,
templatePageSize,
...rest
} = params
// Map renamed fields back to tool parameter names
return {
...rest,
...(mailSubject && { subject: mailSubject }),
...(mailTemplateId && { templateId: mailTemplateId }),
...(listName && { name: listName }),
...(templateName && { name: templateName }),
...(versionName && { name: versionName }),
...(templateSubject && { subject: templateSubject }),
...(contactListIds && { listIds: contactListIds }),
...(templateGenerations && { generations: templateGenerations }),
...(listPageSize && { pageSize: listPageSize }),
...(templatePageSize && { pageSize: templatePageSize }),
}
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
apiKey: { type: 'string', description: 'SendGrid API key' },
// Mail inputs
from: { type: 'string', description: 'Sender email address' },
fromName: { type: 'string', description: 'Sender name' },
to: { type: 'string', description: 'Recipient email address' },
toName: { type: 'string', description: 'Recipient name' },
mailSubject: { type: 'string', description: 'Email subject' },
content: { type: 'string', description: 'Email content' },
contentType: { type: 'string', description: 'Content type' },
cc: { type: 'string', description: 'CC email address' },
bcc: { type: 'string', description: 'BCC email address' },
replyTo: { type: 'string', description: 'Reply-to email address' },
replyToName: { type: 'string', description: 'Reply-to name' },
mailTemplateId: { type: 'string', description: 'Template ID for sending mail' },
dynamicTemplateData: { type: 'json', description: 'Dynamic template data' },
attachmentFiles: { type: 'json', description: 'Files to attach (UI upload)' },
attachments: { type: 'array', description: 'Files to attach (UserFile array)' },
// Contact inputs
email: { type: 'string', description: 'Contact email' },
firstName: { type: 'string', description: 'Contact first name' },
lastName: { type: 'string', description: 'Contact last name' },
customFields: { type: 'json', description: 'Custom fields' },
contactId: { type: 'string', description: 'Contact ID' },
contactIds: { type: 'string', description: 'Comma-separated contact IDs' },
contacts: { type: 'json', description: 'Array of contact objects' },
query: { type: 'string', description: 'Search query' },
contactListIds: { type: 'string', description: 'Comma-separated list IDs for contact' },
// List inputs
listName: { type: 'string', description: 'List name' },
listId: { type: 'string', description: 'List ID' },
listPageSize: { type: 'number', description: 'Page size for listing lists' },
// Template inputs
templateName: { type: 'string', description: 'Template name' },
templateId: { type: 'string', description: 'Template ID' },
generation: { type: 'string', description: 'Template generation' },
templateGenerations: { type: 'string', description: 'Filter templates by generation' },
templatePageSize: { type: 'number', description: 'Page size for listing templates' },
versionName: { type: 'string', description: 'Template version name' },
templateSubject: { type: 'string', description: 'Template subject' },
htmlContent: { type: 'string', description: 'HTML content' },
plainContent: { type: 'string', description: 'Plain text content' },
active: { type: 'boolean', description: 'Whether template version is active' },
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
messageId: { type: 'string', description: 'Email message ID (send_mail)' },
id: { type: 'string', description: 'Resource ID' },
jobId: { type: 'string', description: 'Job ID for async operations' },
email: { type: 'string', description: 'Email address' },
firstName: { type: 'string', description: 'First name' },
lastName: { type: 'string', description: 'Last name' },
contacts: { type: 'json', description: 'Array of contacts' },
contactCount: { type: 'number', description: 'Number of contacts' },
lists: { type: 'json', description: 'Array of lists' },
templates: { type: 'json', description: 'Array of templates' },
message: { type: 'string', description: 'Status or success message' },
name: { type: 'string', description: 'Resource name' },
generation: { type: 'string', description: 'Template generation' },
},
}

View File

@@ -0,0 +1,209 @@
import { SmtpIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { SmtpSendMailResult } from '@/tools/smtp/types'
export const SmtpBlock: BlockConfig<SmtpSendMailResult> = {
type: 'smtp',
name: 'SMTP',
description: 'Send emails via any SMTP mail server',
longDescription:
'Send emails using any SMTP server (Gmail, Outlook, custom servers, etc.). Configure SMTP connection settings and send emails with full control over content, recipients, and attachments.',
docsLink: 'https://docs.sim.ai/tools/smtp',
category: 'tools',
bgColor: '#4A5568',
icon: SmtpIcon,
authMode: AuthMode.ApiKey,
subBlocks: [
{
id: 'smtpHost',
title: 'SMTP Host',
type: 'short-input',
placeholder: 'smtp.gmail.com, smtp.example.com',
required: true,
},
{
id: 'smtpPort',
title: 'SMTP Port',
type: 'short-input',
placeholder: '587',
required: true,
value: () => '587',
},
{
id: 'smtpUsername',
title: 'SMTP Username',
type: 'short-input',
placeholder: 'your-email@example.com',
required: true,
},
{
id: 'smtpPassword',
title: 'SMTP Password',
type: 'short-input',
placeholder: 'Your SMTP password',
required: true,
password: true,
},
{
id: 'smtpSecure',
title: 'Security Mode',
type: 'dropdown',
options: [
{ label: 'TLS (Port 587)', id: 'TLS' },
{ label: 'SSL (Port 465)', id: 'SSL' },
{ label: 'None (Port 25)', id: 'None' },
],
value: () => 'TLS',
required: true,
},
{
id: 'from',
title: 'From',
type: 'short-input',
placeholder: 'sender@example.com',
required: true,
},
{
id: 'to',
title: 'To',
type: 'short-input',
placeholder: 'recipient@example.com',
required: true,
},
{
id: 'subject',
title: 'Subject',
type: 'short-input',
placeholder: 'Email subject',
required: true,
},
{
id: 'body',
title: 'Body',
type: 'long-input',
placeholder: 'Email content',
required: true,
},
{
id: 'contentType',
title: 'Content Type',
type: 'dropdown',
options: [
{ label: 'Plain Text', id: 'text' },
{ label: 'HTML', id: 'html' },
],
value: () => 'text',
required: false,
},
// Attachments Section
// File upload (basic mode)
{
id: 'attachmentFiles',
title: 'Attachments',
type: 'file-upload',
canonicalParamId: 'attachments',
placeholder: 'Upload files to attach',
mode: 'basic',
multiple: true,
required: false,
},
// Variable reference (advanced mode)
{
id: 'attachments',
title: 'Attachments',
type: 'short-input',
canonicalParamId: 'attachments',
placeholder: 'Reference files from previous blocks',
mode: 'advanced',
required: false,
},
// Advanced Options Section
{
id: 'fromName',
title: 'From Name',
type: 'short-input',
placeholder: 'Display name for sender',
mode: 'advanced',
required: false,
},
{
id: 'cc',
title: 'CC',
type: 'short-input',
placeholder: 'cc1@example.com, cc2@example.com',
mode: 'advanced',
required: false,
},
{
id: 'bcc',
title: 'BCC',
type: 'short-input',
placeholder: 'bcc1@example.com, bcc2@example.com',
mode: 'advanced',
required: false,
},
{
id: 'replyTo',
title: 'Reply To',
type: 'short-input',
placeholder: 'reply@example.com',
mode: 'advanced',
required: false,
},
],
tools: {
access: ['smtp_send_mail'],
config: {
tool: () => 'smtp_send_mail',
params: (params) => ({
smtpHost: params.smtpHost,
smtpPort: Number(params.smtpPort),
smtpUsername: params.smtpUsername,
smtpPassword: params.smtpPassword,
smtpSecure: params.smtpSecure,
from: params.from,
to: params.to,
subject: params.subject,
body: params.body,
contentType: params.contentType,
fromName: params.fromName,
cc: params.cc,
bcc: params.bcc,
replyTo: params.replyTo,
attachments: params.attachments,
}),
},
},
inputs: {
smtpHost: { type: 'string', description: 'SMTP server hostname' },
smtpPort: { type: 'number', description: 'SMTP server port' },
smtpUsername: { type: 'string', description: 'SMTP authentication username' },
smtpPassword: { type: 'string', description: 'SMTP authentication password' },
smtpSecure: { type: 'string', description: 'Security protocol (TLS, SSL, or None)' },
from: { type: 'string', description: 'Sender email address' },
to: { type: 'string', description: 'Recipient email address' },
subject: { type: 'string', description: 'Email subject' },
body: { type: 'string', description: 'Email body content' },
contentType: { type: 'string', description: 'Content type (text or html)' },
fromName: { type: 'string', description: 'Display name for sender' },
cc: { type: 'string', description: 'CC recipients (comma-separated)' },
bcc: { type: 'string', description: 'BCC recipients (comma-separated)' },
replyTo: { type: 'string', description: 'Reply-to email address' },
attachments: { type: 'array', description: 'Files to attach (UserFile array)' },
},
outputs: {
success: { type: 'boolean', description: 'Whether the email was sent successfully' },
messageId: { type: 'string', description: 'Message ID from SMTP server' },
to: { type: 'string', description: 'Recipient email address' },
subject: { type: 'string', description: 'Email subject' },
error: { type: 'string', description: 'Error message if sending failed' },
},
}

View File

@@ -41,8 +41,10 @@ import { JinaBlock } from '@/blocks/blocks/jina'
import { JiraBlock } from '@/blocks/blocks/jira'
import { KnowledgeBlock } from '@/blocks/blocks/knowledge'
import { LinearBlock } from '@/blocks/blocks/linear'
import { LinkedInBlock } from '@/blocks/blocks/linkedin'
import { LinkupBlock } from '@/blocks/blocks/linkup'
import { MailchimpBlock } from '@/blocks/blocks/mailchimp'
import { MailgunBlock } from '@/blocks/blocks/mailgun'
import { ManualTriggerBlock } from '@/blocks/blocks/manual_trigger'
import { McpBlock } from '@/blocks/blocks/mcp'
import { Mem0Block } from '@/blocks/blocks/mem0'
@@ -74,10 +76,12 @@ import { RouterBlock } from '@/blocks/blocks/router'
import { S3Block } from '@/blocks/blocks/s3'
import { SalesforceBlock } from '@/blocks/blocks/salesforce'
import { ScheduleBlock } from '@/blocks/blocks/schedule'
import { SendGridBlock } from '@/blocks/blocks/sendgrid'
import { SentryBlock } from '@/blocks/blocks/sentry'
import { SerperBlock } from '@/blocks/blocks/serper'
import { SharepointBlock } from '@/blocks/blocks/sharepoint'
import { SlackBlock } from '@/blocks/blocks/slack'
import { SmtpBlock } from '@/blocks/blocks/smtp'
import { StagehandBlock } from '@/blocks/blocks/stagehand'
import { StagehandAgentBlock } from '@/blocks/blocks/stagehand_agent'
import { StartTriggerBlock } from '@/blocks/blocks/start_trigger'
@@ -152,6 +156,7 @@ export const registry: Record<string, BlockConfig> = {
jira: JiraBlock,
knowledge: KnowledgeBlock,
linear: LinearBlock,
linkedin: LinkedInBlock,
linkup: LinkupBlock,
mailchimp: MailchimpBlock,
mcp: McpBlock,
@@ -178,6 +183,9 @@ export const registry: Record<string, BlockConfig> = {
pylon: PylonBlock,
qdrant: QdrantBlock,
resend: ResendBlock,
sendgrid: SendGridBlock,
mailgun: MailgunBlock,
smtp: SmtpBlock,
memory: MemoryBlock,
reddit: RedditBlock,
response: ResponseBlock,

View File

@@ -1464,11 +1464,17 @@ export function DiscordIcon(props: SVGProps<SVGSVGElement>) {
export function LinkedInIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='4 4 42 42' width='1em' height='1em'>
<svg {...props} height='72' viewBox='0 0 72 72' width='72' xmlns='http://www.w3.org/2000/svg'>
<g fill='none' fillRule='evenodd'>
<path
fill='currentColor'
d='M41,4H9C6.24,4,4,6.24,4,9v32c0,2.76,2.24,5,5,5h32c2.76,0,5-2.24,5-5V9C46,6.24,43.76,4,41,4z M17,20v19h-6V20H17z M11,14.47c0-1.4,1.2-2.47,3-2.47s2.93,1.07,3,2.47c0,1.4-1.12,2.53-3,2.53C12.2,17,11,15.87,11,14.47z M39,39h-6c0,0,0-9.26,0-10 c0-2-1-4-3.5-4.04h-0.08C27,24.96,26,27.02,26,29c0,0.91,0,10,0,10h-6V20h6v2.56c0,0,1.93-2.56,5.81-2.56 c3.97,0,7.19,2.73,7.19,8.26V39z'
d='M8,72 L64,72 C68.418278,72 72,68.418278 72,64 L72,8 C72,3.581722 68.418278,-8.11624501e-16 64,0 L8,0 C3.581722,8.11624501e-16 -5.41083001e-16,3.581722 0,8 L0,64 C5.41083001e-16,68.418278 3.581722,72 8,72 Z'
fill='#0072B1'
/>
<path
d='M62,62 L51.315625,62 L51.315625,43.8021149 C51.315625,38.8127542 49.4197917,36.0245323 45.4707031,36.0245323 C41.1746094,36.0245323 38.9300781,38.9261103 38.9300781,43.8021149 L38.9300781,62 L28.6333333,62 L28.6333333,27.3333333 L38.9300781,27.3333333 L38.9300781,32.0029283 C38.9300781,32.0029283 42.0260417,26.2742151 49.3825521,26.2742151 C56.7356771,26.2742151 62,30.7644705 62,40.051212 L62,62 Z M16.349349,22.7940133 C12.8420573,22.7940133 10,19.9296567 10,16.3970067 C10,12.8643566 12.8420573,10 16.349349,10 C19.8566406,10 22.6970052,12.8643566 22.6970052,16.3970067 C22.6970052,19.9296567 19.8566406,22.7940133 16.349349,22.7940133 Z M11.0325521,62 L21.769401,62 L21.769401,27.3333333 L11.0325521,27.3333333 L11.0325521,62 Z'
fill='#FFF'
/>
</g>
</svg>
)
}
@@ -4344,3 +4350,81 @@ export function PylonIcon(props: SVGProps<SVGSVGElement>) {
</svg>
)
}
export function SendgridIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
width='800px'
height='800px'
viewBox='0 0 256 256'
version='1.1'
xmlns='http://www.w3.org/2000/svg'
xmlnsXlink='http://www.w3.org/1999/xlink'
preserveAspectRatio='xMidYMid'
>
<g>
<path
d='M256.000405,0 L256.000405,170.666936 L170.666936,170.666936 L170.666936,255.996382 L0.00201096905,255.996382 L0.002,170.666 L0,170.666936 L0,85.3314569 L85.3334681,85.3314569 L85.3334681,0 L256.000405,0 Z'
fill='#9DD6E3'
/>
<polygon
fill='#3F72AB'
points='0.00201096905 255.996382 85.3354791 255.996382 85.3354791 170.662915 0.00201096905 170.662915'
/>
<polygon
fill='#00A9D1'
points='170.666936 170.666936 256.000405 170.666936 256.000405 85.3314569 170.666936 85.3314569'
/>
<polygon
fill='#00A9D1'
points='85.3334681 85.3334679 170.666936 85.3334679 170.666936 0 85.3334681 0'
/>
<polygon
fill='#2191C4'
points='85.3334681 170.664925 170.666936 170.664925 170.666936 85.3314569 85.3334681 85.3314569'
/>
<polygon
fill='#3F72AB'
points='170.666936 85.3334679 256.000405 85.3334679 256.000405 0 170.666936 0'
/>
</g>
</svg>
)
}
export function MailgunIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
fill='currentColor'
xmlns='http://www.w3.org/2000/svg'
xmlSpace='preserve'
viewBox='0 0 512 512'
>
<path d='M256.5 159.5c-53.5 0-97 43.5-97 97s43.5 97 97 97 97-43.5 97-97-43.5-97-97-97m-151.1 97c0-83.4 67.7-151.1 151.1-151.1s151.1 67.7 151.1 151.1c0 5.8-.5 11-1 16.3-1 14.7 9.4 25.7 24.1 25.7 24.7 0 27.3-32 27.3-42.5 0-111.7-90.2-202-202-202S54 144.3 54 256s90.2 202 202 202c59.3 0 112.3-25.7 149.5-66.1l41.4 34.6C400.3 479 332.1 512 256 512 114.4 512 0 397.1 0 256 0 114.4 114.9 0 256 0c141.6 0 256 114.9 256 256 0 56.7-27.3 102.8-81.3 102.8-24.1 0-38.3-11-46.7-23.1-26.8 43-74 71.3-128.5 71.3-82.4.6-150.1-67.1-150.1-150.5m151.1-44.6c24.7 0 44.6 19.9 44.6 44.1 0 24.7-19.9 44.6-44.6 44.6s-44.6-19.9-44.6-44.6c.6-24.1 20-44.1 44.6-44.1' />
</svg>
)
}
export function SmtpIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
width='30'
height='24'
viewBox='0 0 30 24'
fill='none'
xmlns='http://www.w3.org/2000/svg'
>
<path
d='M2.35742 5.83288L11.7674 12.1071C13.0656 12.9712 13.7141 13.404 14.4151 13.5725C15.0352 13.7208 15.681 13.7208 16.2998 13.5725C17.0008 13.404 17.6492 12.9712 18.9475 12.1071L28.3574 5.83288M8.82844 21.7219H21.8864C24.1513 21.7219 25.2837 21.7219 26.1492 21.2811C26.9097 20.8931 27.5278 20.2744 27.9152 19.5137C28.3574 18.6482 28.3574 17.5158 28.3574 15.2509V7.97102C28.3574 5.70616 28.3574 4.57373 27.9166 3.70823C27.5288 2.94727 26.9102 2.32858 26.1492 1.94084C25.2837 1.5 24.1513 1.5 21.8864 1.5H8.82844C6.56358 1.5 5.43115 1.5 4.56566 1.94084C3.80519 2.32881 3.187 2.94747 2.79961 3.70823C2.35742 4.57373 2.35742 5.70616 2.35742 7.97102V15.2509C2.35742 17.5158 2.35742 18.6482 2.79826 19.5137C3.186 20.2747 3.80469 20.8933 4.56566 21.2811C5.43115 21.7219 6.56358 21.7219 8.82844 21.7219Z'
stroke='currentColor'
strokeWidth='2.5'
strokeLinecap='round'
strokeLinejoin='round'
/>
<circle cx='24' cy='6' r='4' fill='currentColor' stroke='none' />
</svg>
)
}

View File

@@ -157,6 +157,7 @@ export const auth = betterAuth({
'asana',
'pipedrive',
'hubspot',
'linkedin',
// Common SSO provider patterns
...SSO_TRUSTED_PROVIDERS,
@@ -1585,6 +1586,54 @@ export const auth = betterAuth({
}
},
},
// LinkedIn provider
{
providerId: 'linkedin',
clientId: env.LINKEDIN_CLIENT_ID as string,
clientSecret: env.LINKEDIN_CLIENT_SECRET as string,
authorizationUrl: 'https://www.linkedin.com/oauth/v2/authorization',
tokenUrl: 'https://www.linkedin.com/oauth/v2/accessToken',
userInfoUrl: 'https://api.linkedin.com/v2/userinfo',
scopes: ['profile', 'openid', 'email', 'w_member_social'],
responseType: 'code',
accessType: 'offline',
prompt: 'consent',
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/linkedin`,
getUserInfo: async (tokens) => {
try {
logger.info('Fetching LinkedIn user profile')
const response = await fetch('https://api.linkedin.com/v2/userinfo', {
headers: {
Authorization: `Bearer ${tokens.accessToken}`,
},
})
if (!response.ok) {
logger.error('Failed to fetch LinkedIn user info', {
status: response.status,
statusText: response.statusText,
})
throw new Error('Failed to fetch user info')
}
const profile = await response.json()
return {
id: profile.sub,
name: profile.name || 'LinkedIn User',
email: profile.email || `${profile.sub}@linkedin.user`,
emailVerified: profile.email_verified || true,
image: profile.picture || undefined,
createdAt: new Date(),
updatedAt: new Date(),
}
} catch (error) {
logger.error('Error in LinkedIn getUserInfo:', { error })
return null
}
},
},
],
}),
// Include SSO plugin when enabled

View File

@@ -217,6 +217,8 @@ export const env = createEnv({
WEBFLOW_CLIENT_ID: z.string().optional(), // Webflow OAuth client ID
WEBFLOW_CLIENT_SECRET: z.string().optional(), // Webflow OAuth client secret
TRELLO_API_KEY: z.string().optional(), // Trello API Key
LINKEDIN_CLIENT_ID: z.string().optional(), // LinkedIn OAuth client ID
LINKEDIN_CLIENT_SECRET: z.string().optional(), // LinkedIn OAuth client secret
// E2B Remote Code Execution
E2B_ENABLED: z.string().optional(), // Enable E2B remote code execution

View File

@@ -15,6 +15,7 @@ import {
HubspotIcon,
JiraIcon,
LinearIcon,
LinkedInIcon,
MicrosoftExcelIcon,
MicrosoftIcon,
MicrosoftOneDriveIcon,
@@ -59,6 +60,7 @@ export type OAuthProvider =
| 'pipedrive'
| 'hubspot'
| 'salesforce'
| 'linkedin'
| string
export type OAuthService =
@@ -94,6 +96,7 @@ export type OAuthService =
| 'pipedrive'
| 'hubspot'
| 'salesforce'
| 'linkedin'
export interface OAuthProviderConfig {
id: OAuthProvider
name: string
@@ -732,6 +735,23 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
},
defaultService: 'hubspot',
},
linkedin: {
id: 'linkedin',
name: 'LinkedIn',
icon: (props) => LinkedInIcon(props),
services: {
linkedin: {
id: 'linkedin',
name: 'LinkedIn',
description: 'Share posts and access profile data on LinkedIn.',
providerId: 'linkedin',
icon: (props) => LinkedInIcon(props),
baseProviderIcon: (props) => LinkedInIcon(props),
scopes: ['profile', 'openid', 'email', 'w_member_social'],
},
},
defaultService: 'linkedin',
},
salesforce: {
id: 'salesforce',
name: 'Salesforce',
@@ -1219,6 +1239,19 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig {
supportsRefreshTokenRotation: true,
}
}
case 'linkedin': {
const { clientId, clientSecret } = getCredentials(
env.LINKEDIN_CLIENT_ID,
env.LINKEDIN_CLIENT_SECRET
)
return {
tokenEndpoint: 'https://www.linkedin.com/oauth/v2/accessToken',
clientId,
clientSecret,
useBasicAuth: false,
supportsRefreshTokenRotation: false,
}
}
case 'salesforce': {
const { clientId, clientSecret } = getCredentials(
env.SALESFORCE_CLIENT_ID,

View File

@@ -0,0 +1,56 @@
import type { GetProfileParams, GetProfileResponse } from '@/tools/linkedin/types'
import type { ToolConfig } from '@/tools/types'
export const linkedInGetProfileTool: ToolConfig<GetProfileParams, GetProfileResponse> = {
id: 'linkedin_get_profile',
name: 'Get LinkedIn Profile',
description: 'Retrieve your LinkedIn profile information',
version: '1.0.0',
oauth: {
required: true,
provider: 'linkedin',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for LinkedIn API',
},
},
request: {
url: () => 'https://api.linkedin.com/v2/userinfo',
method: 'GET',
headers: (params: GetProfileParams): Record<string, string> => ({
Authorization: `Bearer ${params.accessToken}`,
'X-Restli-Protocol-Version': '2.0.0',
}),
},
transformResponse: async (response: Response): Promise<GetProfileResponse> => {
if (!response.ok) {
return {
success: false,
output: {},
error: `Failed to get profile: ${response.statusText}`,
}
}
const profile = await response.json()
return {
success: true,
output: {
profile: {
id: profile.sub,
name: profile.name,
email: profile.email,
picture: profile.picture,
},
},
}
},
}

View File

@@ -0,0 +1,2 @@
export { linkedInGetProfileTool } from './get_profile'
export { linkedInSharePostTool } from './share_post'

View File

@@ -0,0 +1,157 @@
import type {
LinkedInProfileOutput,
ProfileIdExtractor,
SharePostParams,
SharePostResponse,
} from '@/tools/linkedin/types'
import type { ToolConfig } from '@/tools/types'
// Helper function to extract profile ID from various response formats
const extractProfileId: ProfileIdExtractor = (output: unknown): string | null => {
if (typeof output === 'object' && output !== null) {
const profileOutput = output as LinkedInProfileOutput
return profileOutput.profile?.id || profileOutput.sub || profileOutput.id || null
}
return null
}
export const linkedInSharePostTool: ToolConfig<SharePostParams, SharePostResponse> = {
id: 'linkedin_share_post',
name: 'Share Post on LinkedIn',
description: 'Share a post to your personal LinkedIn feed',
version: '1.0.0',
oauth: {
required: true,
provider: 'linkedin',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for LinkedIn API',
},
text: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The text content of your LinkedIn post',
},
visibility: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Who can see this post: "PUBLIC" or "CONNECTIONS" (default: "PUBLIC")',
},
},
// First request: Get user profile to obtain the person URN
request: {
url: () => 'https://api.linkedin.com/v2/userinfo',
method: 'GET',
headers: (params: SharePostParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'X-Restli-Protocol-Version': '2.0.0',
}),
},
// Use postProcess to make the actual post creation request
postProcess: async (profileResult, params, executeTool) => {
try {
// Extract profile from the first request
if (!profileResult.success || !profileResult.output) {
return {
success: false,
output: {},
error: 'Failed to fetch user profile',
}
}
// Get profile data from output
const profileOutput = profileResult.output as LinkedInProfileOutput
const authorId = extractProfileId(profileOutput)
if (!authorId) {
return {
success: false,
output: {},
error: 'Could not extract LinkedIn profile ID from response',
}
}
const authorUrn = `urn:li:person:${authorId}`
// Create the post
const postData = {
author: authorUrn,
lifecycleState: 'PUBLISHED',
specificContent: {
'com.linkedin.ugc.ShareContent': {
shareCommentary: {
text: params.text,
},
shareMediaCategory: 'NONE',
},
},
visibility: {
'com.linkedin.ugc.MemberNetworkVisibility': params.visibility || 'PUBLIC',
},
}
const response = await fetch('https://api.linkedin.com/v2/ugcPosts', {
method: 'POST',
headers: {
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
'X-Restli-Protocol-Version': '2.0.0',
},
body: JSON.stringify(postData),
})
if (!response.ok) {
const error = await response.text()
return {
success: false,
output: {},
error: `LinkedIn API error: ${error}`,
}
}
const result = await response.json()
return {
success: true,
output: {
postId: result.id,
},
}
} catch (error) {
return {
success: false,
output: {},
error: error instanceof Error ? error.message : 'Unknown error',
}
}
},
transformResponse: async (response: Response): Promise<SharePostResponse> => {
// This handles the initial profile fetch response
if (!response.ok) {
return {
success: false,
output: {},
error: `Failed to fetch profile: ${response.statusText}`,
}
}
const profile = await response.json()
// Return profile data for postProcess to use
return {
success: true,
output: profile,
}
},
}

View File

@@ -0,0 +1,89 @@
import type { ToolResponse } from '@/tools/types'
export interface LinkedInProfile {
sub: string
name: string
given_name: string
family_name: string
email?: string
picture?: string
email_verified?: boolean
}
export interface LinkedInPost {
author: string // URN format: urn:li:person:abc123
lifecycleState: 'PUBLISHED'
specificContent: {
'com.linkedin.ugc.ShareContent': {
shareCommentary: {
text: string
}
shareMediaCategory: 'NONE' | 'ARTICLE' | 'IMAGE'
media?: Array<{
status: 'READY'
description: {
text: string
}
media: string // URN format
title: {
text: string
}
}>
}
}
visibility: {
'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC' | 'CONNECTIONS'
}
}
export type LinkedInResponse = {
success: boolean
output: {
postId?: string
profile?: {
id: string
name: string
email?: string
picture?: string
}
}
error?: string
}
// Tool-specific type definitions
export interface LinkedInProfileOutput {
profile?: {
id: string
name?: string
email?: string
picture?: string
}
sub?: string
id?: string
[key: string]: unknown
}
export interface SharePostParams {
accessToken: string
text: string
visibility?: 'PUBLIC' | 'CONNECTIONS' | 'LOGGED_IN'
mediaUrls?: string
}
export interface SharePostResponse extends ToolResponse {
output: {
postId?: string
postUrl?: string
visibility?: string
}
}
export interface GetProfileParams {
accessToken: string
}
export interface GetProfileResponse extends ToolResponse {
output: LinkedInProfileOutput
}
export type ProfileIdExtractor = (output: unknown) => string | null

View File

@@ -1,6 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
import { buildMailchimpUrl, handleMailchimpError, type MailchimpMember } from './types'
const logger = createLogger('MailchimpAddMember')
@@ -16,7 +16,7 @@ export interface MailchimpAddMemberParams {
export interface MailchimpAddMemberResponse {
success: boolean
output: {
member: any
member: MailchimpMember
metadata: {
operation: 'add_member'
subscriberHash: string
@@ -81,7 +81,7 @@ export const mailchimpAddMemberTool: ToolConfig<
'Content-Type': 'application/json',
}),
body: (params) => {
const body: any = {
const body: Record<string, unknown> = {
email_address: params.emailAddress,
status: params.status,
}

View File

@@ -1,6 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
import { buildMailchimpUrl, handleMailchimpError, type MailchimpMember } from './types'
const logger = createLogger('MailchimpAddOrUpdateMember')
@@ -17,7 +17,7 @@ export interface MailchimpAddOrUpdateMemberParams {
export interface MailchimpAddOrUpdateMemberResponse {
success: boolean
output: {
member: any
member: MailchimpMember
metadata: {
operation: 'add_or_update_member'
subscriberHash: string
@@ -89,7 +89,7 @@ export const mailchimpAddOrUpdateMemberTool: ToolConfig<
'Content-Type': 'application/json',
}),
body: (params) => {
const body: any = {
const body: Record<string, unknown> = {
email_address: params.emailAddress,
status_if_new: params.statusIfNew,
}

View File

@@ -1,6 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
import { buildMailchimpUrl, handleMailchimpError, type MailchimpMember } from './types'
const logger = createLogger('MailchimpAddSegmentMember')
@@ -14,7 +14,7 @@ export interface MailchimpAddSegmentMemberParams {
export interface MailchimpAddSegmentMemberResponse {
success: boolean
output: {
member: any
member: MailchimpMember
metadata: {
operation: 'add_segment_member'
segmentId: string

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpMember } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpAddSubscriberToAutomation')
@@ -14,7 +15,7 @@ export interface MailchimpAddSubscriberToAutomationParams {
export interface MailchimpAddSubscriberToAutomationResponse {
success: boolean
output: {
subscriber: any
subscriber: MailchimpMember
metadata: {
operation: 'add_subscriber_to_automation'
workflowId: string

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpAudience } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpCreateAudience')
@@ -16,7 +17,7 @@ export interface MailchimpCreateAudienceParams {
export interface MailchimpCreateAudienceResponse {
success: boolean
output: {
list: any
list: MailchimpAudience
metadata: {
operation: 'create_audience'
listId: string
@@ -81,7 +82,7 @@ export const mailchimpCreateAudienceTool: ToolConfig<
'Content-Type': 'application/json',
}),
body: (params) => {
const body: any = {
const body: Record<string, unknown> = {
name: params.audienceName,
permission_reminder: params.permissionReminder,
email_type_option: params.emailTypeOption === 'true',

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpBatchOperation } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpCreateBatchOperation')
@@ -12,7 +13,7 @@ export interface MailchimpCreateBatchOperationParams {
export interface MailchimpCreateBatchOperationResponse {
success: boolean
output: {
batch: any
batch: MailchimpBatchOperation
metadata: {
operation: 'create_batch_operation'
batchId: string

View File

@@ -1,6 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
import { buildMailchimpUrl, handleMailchimpError, type MailchimpCampaign } from './types'
const logger = createLogger('MailchimpCreateCampaign')
@@ -14,7 +14,7 @@ export interface MailchimpCreateCampaignParams {
export interface MailchimpCreateCampaignResponse {
success: boolean
output: {
campaign: any
campaign: MailchimpCampaign
metadata: {
operation: 'create_campaign'
campaignId: string
@@ -67,7 +67,7 @@ export const mailchimpCreateCampaignTool: ToolConfig<
'Content-Type': 'application/json',
}),
body: (params) => {
const body: any = {
const body: Record<string, unknown> = {
type: params.campaignType,
}

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpInterest } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpCreateInterest')
@@ -14,7 +15,7 @@ export interface MailchimpCreateInterestParams {
export interface MailchimpCreateInterestResponse {
success: boolean
output: {
interest: any
interest: MailchimpInterest
metadata: {
operation: 'create_interest'
interestId: string

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpInterestCategory } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpCreateInterestCategory')
@@ -14,7 +15,7 @@ export interface MailchimpCreateInterestCategoryParams {
export interface MailchimpCreateInterestCategoryResponse {
success: boolean
output: {
category: any
category: MailchimpInterestCategory
metadata: {
operation: 'create_interest_category'
interestCategoryId: string

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpLandingPage } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpCreateLandingPage')
@@ -13,7 +14,7 @@ export interface MailchimpCreateLandingPageParams {
export interface MailchimpCreateLandingPageResponse {
success: boolean
output: {
landingPage: any
landingPage: MailchimpLandingPage
metadata: {
operation: 'create_landing_page'
pageId: string
@@ -60,7 +61,7 @@ export const mailchimpCreateLandingPageTool: ToolConfig<
'Content-Type': 'application/json',
}),
body: (params) => {
const body: any = {
const body: Record<string, unknown> = {
type: params.landingPageType,
}

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpMergeField } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpCreateMergeField')
@@ -14,7 +15,7 @@ export interface MailchimpCreateMergeFieldParams {
export interface MailchimpCreateMergeFieldResponse {
success: boolean
output: {
mergeField: any
mergeField: MailchimpMergeField
metadata: {
operation: 'create_merge_field'
mergeId: string

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpSegment } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpCreateSegment')
@@ -14,7 +15,7 @@ export interface MailchimpCreateSegmentParams {
export interface MailchimpCreateSegmentResponse {
success: boolean
output: {
segment: any
segment: MailchimpSegment
metadata: {
operation: 'create_segment'
segmentId: string
@@ -67,7 +68,7 @@ export const mailchimpCreateSegmentTool: ToolConfig<
'Content-Type': 'application/json',
}),
body: (params) => {
const body: any = {
const body: Record<string, unknown> = {
name: params.segmentName,
}

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpAudience } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpGetAudience')
@@ -12,7 +13,7 @@ export interface MailchimpGetAudienceParams {
export interface MailchimpGetAudienceResponse {
success: boolean
output: {
list: any
list: MailchimpAudience
metadata: {
operation: 'get_audience'
listId: string

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpAudience } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpGetAudiences')
@@ -13,7 +14,7 @@ export interface MailchimpGetAudiencesParams {
export interface MailchimpGetAudiencesResponse {
success: boolean
output: {
lists: any[]
lists: MailchimpAudience[]
totalItems: number
metadata: {
operation: 'get_audiences'

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpAutomation } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpGetAutomation')
@@ -12,7 +13,7 @@ export interface MailchimpGetAutomationParams {
export interface MailchimpGetAutomationResponse {
success: boolean
output: {
automation: any
automation: MailchimpAutomation
metadata: {
operation: 'get_automation'
workflowId: string

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpAutomation } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpGetAutomations')
@@ -13,7 +14,7 @@ export interface MailchimpGetAutomationsParams {
export interface MailchimpGetAutomationsResponse {
success: boolean
output: {
automations: any[]
automations: MailchimpAutomation[]
totalItems: number
metadata: {
operation: 'get_automations'

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpBatchOperation } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpGetBatchOperation')
@@ -12,7 +13,7 @@ export interface MailchimpGetBatchOperationParams {
export interface MailchimpGetBatchOperationResponse {
success: boolean
output: {
batch: any
batch: MailchimpBatchOperation
metadata: {
operation: 'get_batch_operation'
batchId: string

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpBatchOperation } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpGetBatchOperations')
@@ -13,7 +14,7 @@ export interface MailchimpGetBatchOperationsParams {
export interface MailchimpGetBatchOperationsResponse {
success: boolean
output: {
batches: any[]
batches: MailchimpBatchOperation[]
totalItems: number
metadata: {
operation: 'get_batch_operations'

View File

@@ -1,6 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
import { buildMailchimpUrl, handleMailchimpError, type MailchimpCampaign } from './types'
const logger = createLogger('MailchimpGetCampaign')
@@ -12,7 +12,7 @@ export interface MailchimpGetCampaignParams {
export interface MailchimpGetCampaignResponse {
success: boolean
output: {
campaign: any
campaign: MailchimpCampaign
metadata: {
operation: 'get_campaign'
campaignId: string

View File

@@ -1,6 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
import { buildMailchimpUrl, handleMailchimpError, type MailchimpCampaignContent } from './types'
const logger = createLogger('MailchimpGetCampaignContent')
@@ -12,7 +12,7 @@ export interface MailchimpGetCampaignContentParams {
export interface MailchimpGetCampaignContentResponse {
success: boolean
output: {
content: any
content: MailchimpCampaignContent
metadata: {
operation: 'get_campaign_content'
campaignId: string

View File

@@ -1,6 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
import { buildMailchimpUrl, handleMailchimpError, type MailchimpCampaignReport } from './types'
const logger = createLogger('MailchimpGetCampaignReport')
@@ -12,7 +12,7 @@ export interface MailchimpGetCampaignReportParams {
export interface MailchimpGetCampaignReportResponse {
success: boolean
output: {
report: any
report: MailchimpCampaignReport
metadata: {
operation: 'get_campaign_report'
campaignId: string

View File

@@ -1,6 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
import { buildMailchimpUrl, handleMailchimpError, type MailchimpCampaignReport } from './types'
const logger = createLogger('MailchimpGetCampaignReports')
@@ -13,7 +13,7 @@ export interface MailchimpGetCampaignReportsParams {
export interface MailchimpGetCampaignReportsResponse {
success: boolean
output: {
reports: any[]
reports: MailchimpCampaignReport[]
totalItems: number
metadata: {
operation: 'get_campaign_reports'

View File

@@ -1,6 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
import { buildMailchimpUrl, handleMailchimpError, type MailchimpCampaign } from './types'
const logger = createLogger('MailchimpGetCampaigns')
@@ -15,7 +15,7 @@ export interface MailchimpGetCampaignsParams {
export interface MailchimpGetCampaignsResponse {
success: boolean
output: {
campaigns: any[]
campaigns: MailchimpCampaign[]
totalItems: number
metadata: {
operation: 'get_campaigns'

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpInterest } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpGetInterest')
@@ -14,7 +15,7 @@ export interface MailchimpGetInterestParams {
export interface MailchimpGetInterestResponse {
success: boolean
output: {
interest: any
interest: MailchimpInterest
metadata: {
operation: 'get_interest'
interestId: string

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpInterestCategory } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpGetInterestCategory')
@@ -13,7 +14,7 @@ export interface MailchimpGetInterestCategoryParams {
export interface MailchimpGetInterestCategoryResponse {
success: boolean
output: {
category: any
category: MailchimpInterestCategory
metadata: {
operation: 'get_interest_category'
interestCategoryId: string

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpLandingPage } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpGetLandingPage')
@@ -12,7 +13,7 @@ export interface MailchimpGetLandingPageParams {
export interface MailchimpGetLandingPageResponse {
success: boolean
output: {
landingPage: any
landingPage: MailchimpLandingPage
metadata: {
operation: 'get_landing_page'
pageId: string

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpLandingPage } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpGetLandingPages')
@@ -13,7 +14,7 @@ export interface MailchimpGetLandingPagesParams {
export interface MailchimpGetLandingPagesResponse {
success: boolean
output: {
landingPages: any[]
landingPages: MailchimpLandingPage[]
totalItems: number
metadata: {
operation: 'get_landing_pages'

View File

@@ -1,6 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
import { buildMailchimpUrl, handleMailchimpError, type MailchimpMember } from './types'
const logger = createLogger('MailchimpGetMember')
@@ -13,7 +13,7 @@ export interface MailchimpGetMemberParams {
export interface MailchimpGetMemberResponse {
success: boolean
output: {
member: any
member: MailchimpMember
metadata: {
operation: 'get_member'
subscriberHash: string

View File

@@ -1,6 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
import { buildMailchimpUrl, handleMailchimpError, type MailchimpTag } from './types'
const logger = createLogger('MailchimpGetMemberTags')
@@ -13,7 +13,7 @@ export interface MailchimpGetMemberTagsParams {
export interface MailchimpGetMemberTagsResponse {
success: boolean
output: {
tags: any[]
tags: MailchimpTag[]
totalItems: number
metadata: {
operation: 'get_member_tags'

View File

@@ -1,6 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
import { buildMailchimpUrl, handleMailchimpError, type MailchimpMember } from './types'
const logger = createLogger('MailchimpGetMembers')
@@ -15,7 +15,7 @@ export interface MailchimpGetMembersParams {
export interface MailchimpGetMembersResponse {
success: boolean
output: {
members: any[]
members: MailchimpMember[]
totalItems: number
metadata: {
operation: 'get_members'

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpMergeField } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpGetMergeFields')
@@ -14,7 +15,7 @@ export interface MailchimpGetMergeFieldsParams {
export interface MailchimpGetMergeFieldsResponse {
success: boolean
output: {
mergeFields: any[]
mergeFields: MailchimpMergeField[]
totalItems: number
metadata: {
operation: 'get_merge_fields'

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpSegment } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpGetSegment')
@@ -13,7 +14,7 @@ export interface MailchimpGetSegmentParams {
export interface MailchimpGetSegmentResponse {
success: boolean
output: {
segment: any
segment: MailchimpSegment
metadata: {
operation: 'get_segment'
segmentId: string

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpMember } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpGetSegmentMembers')
@@ -15,7 +16,7 @@ export interface MailchimpGetSegmentMembersParams {
export interface MailchimpGetSegmentMembersResponse {
success: boolean
output: {
members: any[]
members: MailchimpMember[]
totalItems: number
metadata: {
operation: 'get_segment_members'

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpTemplate } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpGetTemplate')
@@ -12,7 +13,7 @@ export interface MailchimpGetTemplateParams {
export interface MailchimpGetTemplateResponse {
success: boolean
output: {
template: any
template: MailchimpTemplate
metadata: {
operation: 'get_template'
templateId: string

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpTemplate } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpGetTemplates')
@@ -13,7 +14,7 @@ export interface MailchimpGetTemplatesParams {
export interface MailchimpGetTemplatesResponse {
success: boolean
output: {
templates: any[]
templates: MailchimpTemplate[]
totalItems: number
metadata: {
operation: 'get_templates'

View File

@@ -1,6 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
import { buildMailchimpUrl, handleMailchimpError, type MailchimpCampaignContent } from './types'
const logger = createLogger('MailchimpSetCampaignContent')
@@ -15,7 +15,7 @@ export interface MailchimpSetCampaignContentParams {
export interface MailchimpSetCampaignContentResponse {
success: boolean
output: {
content: any
content: MailchimpCampaignContent
metadata: {
operation: 'set_campaign_content'
campaignId: string
@@ -74,7 +74,7 @@ export const mailchimpSetCampaignContentTool: ToolConfig<
'Content-Type': 'application/json',
}),
body: (params) => {
const body: any = {}
const body: Record<string, unknown> = {}
if (params.html) body.html = params.html
if (params.plainText) body.plain_text = params.plainText

View File

@@ -23,12 +23,377 @@ export interface MailchimpResponse<T> {
paging?: MailchimpPagingInfo
metadata: {
operation: string
[key: string]: any
[key: string]: unknown
}
success: boolean
}
}
// Member/Subscriber
export interface MailchimpMember {
id: string
email_address: string
unique_email_id?: string
status: 'subscribed' | 'unsubscribed' | 'cleaned' | 'pending'
merge_fields?: Record<string, unknown>
interests?: Record<string, boolean>
stats?: {
avg_open_rate?: number
avg_click_rate?: number
}
ip_signup?: string
timestamp_signup?: string
ip_opt?: string
timestamp_opt?: string
member_rating?: number
last_changed?: string
language?: string
vip?: boolean
email_client?: string
location?: {
latitude?: number
longitude?: number
gmtoff?: number
dstoff?: number
country_code?: string
timezone?: string
}
tags?: Array<{ id: number; name: string }>
[key: string]: unknown
}
// Audience/List
export interface MailchimpAudience {
id: string
name: string
contact: {
company: string
address1: string
address2?: string
city: string
state: string
zip: string
country: string
phone?: string
}
permission_reminder: string
campaign_defaults: {
from_name: string
from_email: string
subject: string
language: string
}
email_type_option: boolean
stats?: {
member_count?: number
unsubscribe_count?: number
cleaned_count?: number
member_count_since_send?: number
unsubscribe_count_since_send?: number
cleaned_count_since_send?: number
campaign_count?: number
campaign_last_sent?: string
merge_field_count?: number
avg_sub_rate?: number
avg_unsub_rate?: number
target_sub_rate?: number
open_rate?: number
click_rate?: number
last_sub_date?: string
last_unsub_date?: string
}
date_created?: string
list_rating?: number
subscribe_url_short?: string
subscribe_url_long?: string
visibility?: string
[key: string]: unknown
}
// Campaign
export interface MailchimpCampaign {
id: string
type: 'regular' | 'plaintext' | 'absplit' | 'rss' | 'variate'
create_time?: string
archive_url?: string
long_archive_url?: string
status: 'save' | 'paused' | 'schedule' | 'sending' | 'sent'
emails_sent?: number
send_time?: string
content_type?: string
recipients?: {
list_id: string
list_name?: string
segment_text?: string
recipient_count?: number
}
settings?: {
subject_line?: string
preview_text?: string
title?: string
from_name?: string
reply_to?: string
use_conversation?: boolean
to_name?: string
folder_id?: string
authenticate?: boolean
auto_footer?: boolean
inline_css?: boolean
auto_tweet?: boolean
fb_comments?: boolean
timewarp?: boolean
template_id?: number
drag_and_drop?: boolean
}
tracking?: {
opens?: boolean
html_clicks?: boolean
text_clicks?: boolean
goal_tracking?: boolean
ecomm360?: boolean
google_analytics?: string
clicktale?: string
}
[key: string]: unknown
}
// Campaign Content
export interface MailchimpCampaignContent {
html?: string
plain_text?: string
archive_html?: string
[key: string]: unknown
}
// Campaign Report
export interface MailchimpCampaignReport {
id: string
campaign_title?: string
type?: string
emails_sent?: number
abuse_reports?: number
unsubscribed?: number
send_time?: string
bounces?: {
hard_bounces?: number
soft_bounces?: number
syntax_errors?: number
}
forwards?: {
forwards_count?: number
forwards_opens?: number
}
opens?: {
opens_total?: number
unique_opens?: number
open_rate?: number
last_open?: string
}
clicks?: {
clicks_total?: number
unique_clicks?: number
unique_subscriber_clicks?: number
click_rate?: number
last_click?: string
}
list_stats?: {
sub_rate?: number
unsub_rate?: number
open_rate?: number
click_rate?: number
}
[key: string]: unknown
}
// Automation
export interface MailchimpAutomation {
id: string
create_time?: string
start_time?: string
status: 'save' | 'paused' | 'sending'
emails_sent?: number
recipients?: {
list_id: string
list_name?: string
segment_opts?: unknown
}
settings?: {
title?: string
from_name?: string
reply_to?: string
use_conversation?: boolean
to_name?: string
authenticate?: boolean
auto_footer?: boolean
inline_css?: boolean
}
tracking?: {
opens?: boolean
html_clicks?: boolean
text_clicks?: boolean
goal_tracking?: boolean
ecomm360?: boolean
google_analytics?: string
clicktale?: string
}
[key: string]: unknown
}
// Segment
export interface MailchimpSegment {
id: number
name: string
member_count?: number
type: 'saved' | 'static' | 'fuzzy'
created_at?: string
updated_at?: string
options?: {
match?: 'any' | 'all'
conditions?: Array<{
condition_type?: string
field?: string
op?: string
value?: unknown
}>
}
list_id?: string
[key: string]: unknown
}
// Template
export interface MailchimpTemplate {
id: number
type: string
name: string
drag_and_drop?: boolean
responsive?: boolean
category?: string
date_created?: string
date_edited?: string
created_by?: string
edited_by?: string
active?: boolean
folder_id?: string
thumbnail?: string
share_url?: string
[key: string]: unknown
}
// Landing Page
export interface MailchimpLandingPage {
id: string
name: string
title?: string
description?: string
template_id?: number
status: 'draft' | 'published' | 'unpublished'
list_id?: string
store_id?: string
web_id?: number
created_at?: string
published_at?: string
unpublished_at?: string
updated_at?: string
url?: string
tracking?: {
opens?: boolean
html_clicks?: boolean
text_clicks?: boolean
goal_tracking?: boolean
ecomm360?: boolean
google_analytics?: string
clicktale?: string
}
[key: string]: unknown
}
// Interest Category
export interface MailchimpInterestCategory {
list_id?: string
id: string
title: string
display_order?: number
type: 'checkboxes' | 'dropdown' | 'radio' | 'hidden'
[key: string]: unknown
}
// Interest
export interface MailchimpInterest {
category_id?: string
list_id?: string
id: string
name: string
subscriber_count?: string
display_order?: number
[key: string]: unknown
}
// Merge Field
export interface MailchimpMergeField {
merge_id?: number
tag: string
name: string
type:
| 'text'
| 'number'
| 'address'
| 'phone'
| 'date'
| 'url'
| 'imageurl'
| 'radio'
| 'dropdown'
| 'birthday'
| 'zip'
required?: boolean
default_value?: string
public?: boolean
display_order?: number
options?: {
default_country?: number
phone_format?: string
date_format?: string
choices?: string[]
size?: number
}
help_text?: string
list_id?: string
[key: string]: unknown
}
// Batch Operation
export interface MailchimpBatchOperation {
id: string
status: 'pending' | 'preprocessing' | 'started' | 'finalizing' | 'finished'
total_operations?: number
finished_operations?: number
errored_operations?: number
submitted_at?: string
completed_at?: string
response_body_url?: string
[key: string]: unknown
}
// Tag
export interface MailchimpTag {
id: number
name: string
[key: string]: unknown
}
// Error Response
export interface MailchimpErrorResponse {
type?: string
title?: string
status?: number
detail?: string
instance?: string
errors?: Array<{
field?: string
message?: string
}>
}
// Helper function to extract server prefix from API key
export function extractServerPrefix(apiKey: string): string {
const parts = apiKey.split('-')
@@ -45,9 +410,11 @@ export function buildMailchimpUrl(apiKey: string, path: string): string {
}
// Helper function for consistent error handling
export function handleMailchimpError(data: any, status: number, operation: string): never {
export function handleMailchimpError(data: unknown, status: number, operation: string): never {
logger.error(`Mailchimp API request failed for ${operation}`, { data, status })
const errorMessage = data.detail || data.title || data.error || data.message || 'Unknown error'
const errorData = data as Record<string, unknown>
const errorMessage =
errorData.detail || errorData.title || errorData.error || errorData.message || 'Unknown error'
throw new Error(`Mailchimp ${operation} failed: ${errorMessage}`)
}

View File

@@ -1,6 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
import { buildMailchimpUrl, handleMailchimpError, type MailchimpMember } from './types'
const logger = createLogger('MailchimpUnarchiveMember')
@@ -15,7 +15,7 @@ export interface MailchimpUnarchiveMemberParams {
export interface MailchimpUnarchiveMemberResponse {
success: boolean
output: {
member: any
member: MailchimpMember
metadata: {
operation: 'unarchive_member'
subscriberHash: string

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpAudience } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpUpdateAudience')
@@ -16,7 +17,7 @@ export interface MailchimpUpdateAudienceParams {
export interface MailchimpUpdateAudienceResponse {
success: boolean
output: {
list: any
list: MailchimpAudience
metadata: {
operation: 'update_audience'
listId: string
@@ -81,7 +82,7 @@ export const mailchimpUpdateAudienceTool: ToolConfig<
'Content-Type': 'application/json',
}),
body: (params) => {
const body: any = {}
const body: Record<string, unknown> = {}
if (params.audienceName) body.name = params.audienceName
if (params.permissionReminder) body.permission_reminder = params.permissionReminder

View File

@@ -1,6 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
import { buildMailchimpUrl, handleMailchimpError, type MailchimpCampaign } from './types'
const logger = createLogger('MailchimpUpdateCampaign')
@@ -14,7 +14,7 @@ export interface MailchimpUpdateCampaignParams {
export interface MailchimpUpdateCampaignResponse {
success: boolean
output: {
campaign: any
campaign: MailchimpCampaign
metadata: {
operation: 'update_campaign'
campaignId: string
@@ -67,7 +67,7 @@ export const mailchimpUpdateCampaignTool: ToolConfig<
'Content-Type': 'application/json',
}),
body: (params) => {
const body: any = {}
const body: Record<string, unknown> = {}
if (params.campaignSettings) {
try {

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpInterest } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpUpdateInterest')
@@ -15,7 +16,7 @@ export interface MailchimpUpdateInterestParams {
export interface MailchimpUpdateInterestResponse {
success: boolean
output: {
interest: any
interest: MailchimpInterest
metadata: {
operation: 'update_interest'
interestId: string
@@ -78,7 +79,7 @@ export const mailchimpUpdateInterestTool: ToolConfig<
'Content-Type': 'application/json',
}),
body: (params) => {
const body: any = {}
const body: Record<string, unknown> = {}
if (params.interestName) body.name = params.interestName

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpInterestCategory } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpUpdateInterestCategory')
@@ -14,7 +15,7 @@ export interface MailchimpUpdateInterestCategoryParams {
export interface MailchimpUpdateInterestCategoryResponse {
success: boolean
output: {
category: any
category: MailchimpInterestCategory
metadata: {
operation: 'update_interest_category'
interestCategoryId: string
@@ -71,7 +72,7 @@ export const mailchimpUpdateInterestCategoryTool: ToolConfig<
'Content-Type': 'application/json',
}),
body: (params) => {
const body: any = {}
const body: Record<string, unknown> = {}
if (params.interestCategoryTitle) body.title = params.interestCategoryTitle

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpLandingPage } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpUpdateLandingPage')
@@ -13,7 +14,7 @@ export interface MailchimpUpdateLandingPageParams {
export interface MailchimpUpdateLandingPageResponse {
success: boolean
output: {
landingPage: any
landingPage: MailchimpLandingPage
metadata: {
operation: 'update_landing_page'
pageId: string
@@ -60,7 +61,7 @@ export const mailchimpUpdateLandingPageTool: ToolConfig<
'Content-Type': 'application/json',
}),
body: (params) => {
const body: any = {}
const body: Record<string, unknown> = {}
if (params.landingPageTitle) body.title = params.landingPageTitle

View File

@@ -1,6 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
import { buildMailchimpUrl, handleMailchimpError, type MailchimpMember } from './types'
const logger = createLogger('MailchimpUpdateMember')
@@ -17,7 +17,7 @@ export interface MailchimpUpdateMemberParams {
export interface MailchimpUpdateMemberResponse {
success: boolean
output: {
member: any
member: MailchimpMember
metadata: {
operation: 'update_member'
subscriberHash: string
@@ -89,7 +89,7 @@ export const mailchimpUpdateMemberTool: ToolConfig<
'Content-Type': 'application/json',
}),
body: (params) => {
const body: any = {}
const body: Record<string, unknown> = {}
if (params.emailAddress) body.email_address = params.emailAddress
if (params.status) body.status = params.status

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpMergeField } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpUpdateMergeField')
@@ -14,7 +15,7 @@ export interface MailchimpUpdateMergeFieldParams {
export interface MailchimpUpdateMergeFieldResponse {
success: boolean
output: {
mergeField: any
mergeField: MailchimpMergeField
metadata: {
operation: 'update_merge_field'
mergeId: string
@@ -68,7 +69,7 @@ export const mailchimpUpdateMergeFieldTool: ToolConfig<
'Content-Type': 'application/json',
}),
body: (params) => {
const body: any = {}
const body: Record<string, unknown> = {}
if (params.mergeName) body.name = params.mergeName

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpSegment } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpUpdateSegment')
@@ -15,7 +16,7 @@ export interface MailchimpUpdateSegmentParams {
export interface MailchimpUpdateSegmentResponse {
success: boolean
output: {
segment: any
segment: MailchimpSegment
metadata: {
operation: 'update_segment'
segmentId: string
@@ -75,7 +76,7 @@ export const mailchimpUpdateSegmentTool: ToolConfig<
'Content-Type': 'application/json',
}),
body: (params) => {
const body: any = {}
const body: Record<string, unknown> = {}
if (params.segmentName) body.name = params.segmentName

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import type { MailchimpTemplate } from './types'
import { buildMailchimpUrl, handleMailchimpError } from './types'
const logger = createLogger('MailchimpUpdateTemplate')
@@ -14,7 +15,7 @@ export interface MailchimpUpdateTemplateParams {
export interface MailchimpUpdateTemplateResponse {
success: boolean
output: {
template: any
template: MailchimpTemplate
metadata: {
operation: 'update_template'
templateId: string
@@ -67,7 +68,7 @@ export const mailchimpUpdateTemplateTool: ToolConfig<
'Content-Type': 'application/json',
}),
body: (params) => {
const body: any = {}
const body: Record<string, unknown> = {}
if (params.templateName) body.name = params.templateName
if (params.templateHtml) body.html = params.templateHtml

View File

@@ -0,0 +1,101 @@
import type { AddListMemberParams, AddListMemberResult } from '@/tools/mailgun/types'
import type { ToolConfig } from '@/tools/types'
export const mailgunAddListMemberTool: ToolConfig<AddListMemberParams, AddListMemberResult> = {
id: 'mailgun_add_list_member',
name: 'Mailgun Add List Member',
description: 'Add a member to a mailing list',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Mailgun API key',
},
listAddress: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Mailing list address',
},
address: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Member email address',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Member name',
},
vars: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'JSON string of custom variables',
},
subscribed: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether the member is subscribed',
},
},
request: {
url: (params) => `https://api.mailgun.net/v3/lists/${params.listAddress}/members`,
method: 'POST',
headers: (params) => ({
Authorization: `Basic ${Buffer.from(`api:${params.apiKey}`).toString('base64')}`,
}),
body: (params) => {
const formData = new FormData()
formData.append('address', params.address)
if (params.name) {
formData.append('name', params.name)
}
if (params.vars) {
formData.append('vars', params.vars)
}
if (params.subscribed !== undefined) {
formData.append('subscribed', params.subscribed ? 'yes' : 'no')
}
return { body: formData }
},
},
transformResponse: async (response, params): Promise<AddListMemberResult> => {
if (!response.ok) {
const error = await response.json()
throw new Error(error.message || 'Failed to add list member')
}
const result = await response.json()
return {
success: true,
output: {
success: true,
message: result.message,
member: {
address: result.member.address,
name: result.member.name,
subscribed: result.member.subscribed,
vars: result.member.vars,
},
},
}
},
outputs: {
success: { type: 'boolean', description: 'Whether the member was added successfully' },
message: { type: 'string', description: 'Response message' },
member: { type: 'json', description: 'Added member details' },
},
}

View File

@@ -0,0 +1,99 @@
import type { CreateMailingListParams, CreateMailingListResult } from '@/tools/mailgun/types'
import type { ToolConfig } from '@/tools/types'
export const mailgunCreateMailingListTool: ToolConfig<
CreateMailingListParams,
CreateMailingListResult
> = {
id: 'mailgun_create_mailing_list',
name: 'Mailgun Create Mailing List',
description: 'Create a new mailing list',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Mailgun API key',
},
address: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Mailing list address (e.g., list@example.com)',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Mailing list name',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Mailing list description',
},
accessLevel: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Access level: readonly, members, or everyone',
},
},
request: {
url: () => 'https://api.mailgun.net/v3/lists',
method: 'POST',
headers: (params) => ({
Authorization: `Basic ${Buffer.from(`api:${params.apiKey}`).toString('base64')}`,
}),
body: (params) => {
const formData = new FormData()
formData.append('address', params.address)
if (params.name) {
formData.append('name', params.name)
}
if (params.description) {
formData.append('description', params.description)
}
if (params.accessLevel) {
formData.append('access_level', params.accessLevel)
}
return { body: formData }
},
},
transformResponse: async (response, params): Promise<CreateMailingListResult> => {
if (!response.ok) {
const error = await response.json()
throw new Error(error.message || 'Failed to create mailing list')
}
const result = await response.json()
return {
success: true,
output: {
success: true,
message: result.message,
list: {
address: result.list.address,
name: result.list.name,
description: result.list.description,
accessLevel: result.list.access_level,
createdAt: result.list.created_at,
},
},
}
},
outputs: {
success: { type: 'boolean', description: 'Whether the list was created successfully' },
message: { type: 'string', description: 'Response message' },
list: { type: 'json', description: 'Created mailing list details' },
},
}

View File

@@ -0,0 +1,62 @@
import type { GetDomainParams, GetDomainResult } from '@/tools/mailgun/types'
import type { ToolConfig } from '@/tools/types'
export const mailgunGetDomainTool: ToolConfig<GetDomainParams, GetDomainResult> = {
id: 'mailgun_get_domain',
name: 'Mailgun Get Domain',
description: 'Get details of a specific domain',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Mailgun API key',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Domain name',
},
},
request: {
url: (params) => `https://api.mailgun.net/v3/domains/${params.domain}`,
method: 'GET',
headers: (params) => ({
Authorization: `Basic ${Buffer.from(`api:${params.apiKey}`).toString('base64')}`,
}),
},
transformResponse: async (response, params): Promise<GetDomainResult> => {
if (!response.ok) {
const error = await response.json()
throw new Error(error.message || 'Failed to get domain')
}
const result = await response.json()
return {
success: true,
output: {
success: true,
domain: {
name: result.domain.name,
smtpLogin: result.domain.smtp_login,
smtpPassword: result.domain.smtp_password,
spamAction: result.domain.spam_action,
state: result.domain.state,
createdAt: result.domain.created_at,
type: result.domain.type,
},
},
}
},
outputs: {
success: { type: 'boolean', description: 'Whether the request was successful' },
domain: { type: 'json', description: 'Domain details' },
},
}

View File

@@ -0,0 +1,61 @@
import type { GetMailingListParams, GetMailingListResult } from '@/tools/mailgun/types'
import type { ToolConfig } from '@/tools/types'
export const mailgunGetMailingListTool: ToolConfig<GetMailingListParams, GetMailingListResult> = {
id: 'mailgun_get_mailing_list',
name: 'Mailgun Get Mailing List',
description: 'Get details of a mailing list',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Mailgun API key',
},
address: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Mailing list address',
},
},
request: {
url: (params) => `https://api.mailgun.net/v3/lists/${params.address}`,
method: 'GET',
headers: (params) => ({
Authorization: `Basic ${Buffer.from(`api:${params.apiKey}`).toString('base64')}`,
}),
},
transformResponse: async (response, params): Promise<GetMailingListResult> => {
if (!response.ok) {
const error = await response.json()
throw new Error(error.message || 'Failed to get mailing list')
}
const result = await response.json()
return {
success: true,
output: {
success: true,
list: {
address: result.list.address,
name: result.list.name,
description: result.list.description,
accessLevel: result.list.access_level,
membersCount: result.list.members_count,
createdAt: result.list.created_at,
},
},
}
},
outputs: {
success: { type: 'boolean', description: 'Whether the request was successful' },
list: { type: 'json', description: 'Mailing list details' },
},
}

View File

@@ -0,0 +1,83 @@
import type { GetMessageParams, GetMessageResult } from '@/tools/mailgun/types'
import type { ToolConfig } from '@/tools/types'
export const mailgunGetMessageTool: ToolConfig<GetMessageParams, GetMessageResult> = {
id: 'mailgun_get_message',
name: 'Mailgun Get Message',
description: 'Retrieve a stored message by its key',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Mailgun API key',
},
domain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Mailgun domain',
},
messageKey: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Message storage key',
},
},
request: {
url: (params) =>
`https://api.mailgun.net/v3/domains/${params.domain}/messages/${params.messageKey}`,
method: 'GET',
headers: (params) => ({
Authorization: `Basic ${Buffer.from(`api:${params.apiKey}`).toString('base64')}`,
}),
},
transformResponse: async (response, params): Promise<GetMessageResult> => {
if (!response.ok) {
const error = await response.json()
throw new Error(error.message || 'Failed to get message')
}
const result = await response.json()
return {
success: true,
output: {
success: true,
recipients: result.recipients,
from: result.from,
subject: result.subject,
bodyPlain: result['body-plain'],
strippedText: result['stripped-text'],
strippedSignature: result['stripped-signature'],
bodyHtml: result['body-html'],
strippedHtml: result['stripped-html'],
attachmentCount: result['attachment-count'],
timestamp: result.timestamp,
messageHeaders: result['message-headers'],
contentIdMap: result['content-id-map'],
},
}
},
outputs: {
success: { type: 'boolean', description: 'Whether the request was successful' },
recipients: { type: 'string', description: 'Message recipients' },
from: { type: 'string', description: 'Sender email' },
subject: { type: 'string', description: 'Message subject' },
bodyPlain: { type: 'string', description: 'Plain text body' },
strippedText: { type: 'string', description: 'Stripped text' },
strippedSignature: { type: 'string', description: 'Stripped signature' },
bodyHtml: { type: 'string', description: 'HTML body' },
strippedHtml: { type: 'string', description: 'Stripped HTML' },
attachmentCount: { type: 'number', description: 'Number of attachments' },
timestamp: { type: 'number', description: 'Message timestamp' },
messageHeaders: { type: 'json', description: 'Message headers' },
contentIdMap: { type: 'json', description: 'Content ID map' },
},
}

View File

@@ -0,0 +1,11 @@
// Message Operations
export { mailgunAddListMemberTool } from './add_list_member'
export { mailgunCreateMailingListTool } from './create_mailing_list'
export { mailgunGetDomainTool } from './get_domain'
export { mailgunGetMailingListTool } from './get_mailing_list'
export { mailgunGetMessageTool } from './get_message'
export { mailgunListDomainsTool } from './list_domains'
export { mailgunListMessagesTool } from './list_messages'
export { mailgunSendMessageTool } from './send_message'
export type { SendMessageParams, SendMessageResult } from './types'

View File

@@ -0,0 +1,50 @@
import type { ListDomainsParams, ListDomainsResult } from '@/tools/mailgun/types'
import type { ToolConfig } from '@/tools/types'
export const mailgunListDomainsTool: ToolConfig<ListDomainsParams, ListDomainsResult> = {
id: 'mailgun_list_domains',
name: 'Mailgun List Domains',
description: 'List all domains for your Mailgun account',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Mailgun API key',
},
},
request: {
url: () => 'https://api.mailgun.net/v3/domains',
method: 'GET',
headers: (params) => ({
Authorization: `Basic ${Buffer.from(`api:${params.apiKey}`).toString('base64')}`,
}),
},
transformResponse: async (response, params): Promise<ListDomainsResult> => {
if (!response.ok) {
const error = await response.json()
throw new Error(error.message || 'Failed to list domains')
}
const result = await response.json()
return {
success: true,
output: {
success: true,
totalCount: result.total_count,
items: result.items || [],
},
}
},
outputs: {
success: { type: 'boolean', description: 'Whether the request was successful' },
totalCount: { type: 'number', description: 'Total number of domains' },
items: { type: 'json', description: 'Array of domain objects' },
},
}

View File

@@ -0,0 +1,81 @@
import type { ListMessagesParams, ListMessagesResult } from '@/tools/mailgun/types'
import type { ToolConfig } from '@/tools/types'
export const mailgunListMessagesTool: ToolConfig<ListMessagesParams, ListMessagesResult> = {
id: 'mailgun_list_messages',
name: 'Mailgun List Messages',
description: 'List events (logs) for messages sent through Mailgun',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Mailgun API key',
},
domain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Mailgun domain',
},
event: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by event type (accepted, delivered, failed, opened, clicked, etc.)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of events to return (default: 100)',
},
},
request: {
url: (params) => {
const baseUrl = `https://api.mailgun.net/v3/${params.domain}/events`
const queryParams = new URLSearchParams()
if (params.event) {
queryParams.append('event', params.event)
}
if (params.limit) {
queryParams.append('limit', params.limit.toString())
}
const query = queryParams.toString()
return query ? `${baseUrl}?${query}` : baseUrl
},
method: 'GET',
headers: (params) => ({
Authorization: `Basic ${Buffer.from(`api:${params.apiKey}`).toString('base64')}`,
}),
},
transformResponse: async (response, params): Promise<ListMessagesResult> => {
if (!response.ok) {
const error = await response.json()
throw new Error(error.message || 'Failed to list messages')
}
const result = await response.json()
return {
success: true,
output: {
success: true,
items: result.items || [],
paging: result.paging || {},
},
}
},
outputs: {
success: { type: 'boolean', description: 'Whether the request was successful' },
items: { type: 'json', description: 'Array of event items' },
paging: { type: 'json', description: 'Paging information' },
},
}

View File

@@ -0,0 +1,129 @@
import type { SendMessageParams, SendMessageResult } from '@/tools/mailgun/types'
import type { ToolConfig } from '@/tools/types'
export const mailgunSendMessageTool: ToolConfig<SendMessageParams, SendMessageResult> = {
id: 'mailgun_send_message',
name: 'Mailgun Send Message',
description: 'Send an email using Mailgun API',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Mailgun API key',
},
domain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Mailgun domain (e.g., mg.example.com)',
},
from: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Sender email address',
},
to: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Recipient email address (comma-separated for multiple)',
},
subject: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Email subject',
},
text: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Plain text body of the email',
},
html: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'HTML body of the email',
},
cc: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'CC email address (comma-separated for multiple)',
},
bcc: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'BCC email address (comma-separated for multiple)',
},
tags: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Tags for the email (comma-separated)',
},
},
request: {
url: (params) => `https://api.mailgun.net/v3/${params.domain}/messages`,
method: 'POST',
headers: (params) => ({
Authorization: `Basic ${Buffer.from(`api:${params.apiKey}`).toString('base64')}`,
}),
body: (params) => {
const formData = new FormData()
formData.append('from', params.from)
formData.append('to', params.to)
formData.append('subject', params.subject)
if (params.text) {
formData.append('text', params.text)
}
if (params.html) {
formData.append('html', params.html)
}
if (params.cc) {
formData.append('cc', params.cc)
}
if (params.bcc) {
formData.append('bcc', params.bcc)
}
if (params.tags) {
const tagArray = params.tags.split(',').map((t) => t.trim())
tagArray.forEach((tag) => formData.append('o:tag', tag))
}
return { body: formData }
},
},
transformResponse: async (response, params): Promise<SendMessageResult> => {
if (!response.ok) {
const error = await response.json()
throw new Error(error.message || 'Failed to send message')
}
const result = await response.json()
return {
success: true,
output: {
success: true,
id: result.id,
message: result.message || 'Queued. Thank you.',
},
}
},
outputs: {
success: { type: 'boolean', description: 'Whether the message was sent successfully' },
id: { type: 'string', description: 'Message ID' },
message: { type: 'string', description: 'Response message from Mailgun' },
},
}

View File

@@ -0,0 +1,201 @@
import type { ToolResponse } from '@/tools/types'
export interface MailgunMessageHeaders {
[key: string]: string | string[]
}
export interface MailgunMessageItem {
timestamp: number
event: string
recipient: string
sender?: string
subject?: string
deliveryStatus?: string
[key: string]: unknown
}
export interface MailgunDomainItem {
name: string
state: string
type: string
created_at?: string
smtp_login?: string
[key: string]: unknown
}
export interface MailgunPaging {
first?: string
next?: string
previous?: string
last?: string
}
export interface MailgunMailingListMember {
address: string
name?: string
subscribed: boolean
vars?: Record<string, string | number | boolean | null>
}
// Send Message
export interface SendMessageParams {
apiKey: string
domain: string
from: string
to: string
subject: string
text?: string
html?: string
cc?: string
bcc?: string
tags?: string
}
export interface SendMessageResult extends ToolResponse {
output: {
success: boolean
id: string
message: string
}
}
// Get Message
export interface GetMessageParams {
apiKey: string
domain: string
messageKey: string
}
export interface GetMessageResult extends ToolResponse {
output: {
success: boolean
recipients: string
from: string
subject: string
bodyPlain: string
strippedText: string
strippedSignature: string
bodyHtml: string
strippedHtml: string
attachmentCount: number
timestamp: number
messageHeaders: MailgunMessageHeaders
contentIdMap: Record<string, string>
}
}
// List Messages (Events)
export interface ListMessagesParams {
apiKey: string
domain: string
event?: string
limit?: number
}
export interface ListMessagesResult extends ToolResponse {
output: {
success: boolean
items: MailgunMessageItem[]
paging: MailgunPaging
}
}
// Create Mailing List
export interface CreateMailingListParams {
apiKey: string
address: string
name?: string
description?: string
accessLevel?: 'readonly' | 'members' | 'everyone'
}
export interface CreateMailingListResult extends ToolResponse {
output: {
success: boolean
message: string
list: {
address: string
name: string
description: string
accessLevel: string
createdAt: string
}
}
}
// Get Mailing List
export interface GetMailingListParams {
apiKey: string
address: string
}
export interface GetMailingListResult extends ToolResponse {
output: {
success: boolean
list: {
address: string
name: string
description: string
accessLevel: string
membersCount: number
createdAt: string
}
}
}
// Add List Member
export interface AddListMemberParams {
apiKey: string
listAddress: string
address: string
name?: string
vars?: string
subscribed?: boolean
}
export interface AddListMemberResult extends ToolResponse {
output: {
success: boolean
message: string
member: {
address: string
name: string
subscribed: boolean
vars: Record<string, string | number | boolean | null>
}
}
}
// List Domains
export interface ListDomainsParams {
apiKey: string
}
export interface ListDomainsResult extends ToolResponse {
output: {
success: boolean
totalCount: number
items: MailgunDomainItem[]
}
}
// Get Domain
export interface GetDomainParams {
apiKey: string
domain: string
}
export interface GetDomainResult extends ToolResponse {
output: {
success: boolean
domain: {
name: string
smtpLogin: string
smtpPassword: string
spamAction: string
state: string
createdAt: string
type: string
}
}
}

View File

@@ -419,6 +419,7 @@ import {
linearUpdateProjectTool,
linearUpdateWorkflowStateTool,
} from '@/tools/linear'
import { linkedInGetProfileTool, linkedInSharePostTool } from '@/tools/linkedin'
import { linkupSearchTool } from '@/tools/linkup'
import {
mailchimpAddMemberTagsTool,
@@ -495,6 +496,16 @@ import {
mailchimpUpdateSegmentTool,
mailchimpUpdateTemplateTool,
} from '@/tools/mailchimp'
import {
mailgunAddListMemberTool,
mailgunCreateMailingListTool,
mailgunGetDomainTool,
mailgunGetMailingListTool,
mailgunGetMessageTool,
mailgunListDomainsTool,
mailgunListMessagesTool,
mailgunSendMessageTool,
} from '@/tools/mailgun'
import { mem0AddMemoriesTool, mem0GetMemoriesTool, mem0SearchMemoriesTool } from '@/tools/mem0'
import { memoryAddTool, memoryDeleteTool, memoryGetAllTool, memoryGetTool } from '@/tools/memory'
import {
@@ -756,6 +767,24 @@ import {
salesforceUpdateOpportunityTool,
salesforceUpdateTaskTool,
} from '@/tools/salesforce'
import {
sendGridAddContactsToListTool,
sendGridAddContactTool,
sendGridCreateListTool,
sendGridCreateTemplateTool,
sendGridCreateTemplateVersionTool,
sendGridDeleteContactsTool,
sendGridDeleteListTool,
sendGridDeleteTemplateTool,
sendGridGetContactTool,
sendGridGetListTool,
sendGridGetTemplateTool,
sendGridListAllListsTool,
sendGridListTemplatesTool,
sendGridRemoveContactsFromListTool,
sendGridSearchContactsTool,
sendGridSendMailTool,
} from '@/tools/sendgrid'
import {
createDeployTool,
createProjectTool,
@@ -791,6 +820,7 @@ import {
slackUpdateMessageTool,
} from '@/tools/slack'
import { smsSendTool } from '@/tools/sms'
import { smtpSendMailTool } from '@/tools/smtp'
import { stagehandAgentTool, stagehandExtractTool } from '@/tools/stagehand'
import {
stripeCancelPaymentIntentTool,
@@ -1025,7 +1055,34 @@ export const tools: Record<string, ToolConfig> = {
jina_read_url: readUrlTool,
jina_search: jinaSearchTool,
linkup_search: linkupSearchTool,
linkedin_share_post: linkedInSharePostTool,
linkedin_get_profile: linkedInGetProfileTool,
resend_send: mailSendTool,
sendgrid_send_mail: sendGridSendMailTool,
sendgrid_add_contact: sendGridAddContactTool,
sendgrid_get_contact: sendGridGetContactTool,
sendgrid_search_contacts: sendGridSearchContactsTool,
sendgrid_delete_contacts: sendGridDeleteContactsTool,
sendgrid_create_list: sendGridCreateListTool,
sendgrid_get_list: sendGridGetListTool,
sendgrid_list_all_lists: sendGridListAllListsTool,
sendgrid_delete_list: sendGridDeleteListTool,
sendgrid_add_contacts_to_list: sendGridAddContactsToListTool,
sendgrid_remove_contacts_from_list: sendGridRemoveContactsFromListTool,
sendgrid_create_template: sendGridCreateTemplateTool,
sendgrid_get_template: sendGridGetTemplateTool,
sendgrid_list_templates: sendGridListTemplatesTool,
sendgrid_delete_template: sendGridDeleteTemplateTool,
sendgrid_create_template_version: sendGridCreateTemplateVersionTool,
smtp_send_mail: smtpSendMailTool,
mailgun_send_message: mailgunSendMessageTool,
mailgun_get_message: mailgunGetMessageTool,
mailgun_list_messages: mailgunListMessagesTool,
mailgun_create_mailing_list: mailgunCreateMailingListTool,
mailgun_get_mailing_list: mailgunGetMailingListTool,
mailgun_add_list_member: mailgunAddListMemberTool,
mailgun_list_domains: mailgunListDomainsTool,
mailgun_get_domain: mailgunGetDomainTool,
sms_send: smsSendTool,
jira_retrieve: jiraRetrieveTool,
jira_update: jiraUpdateTool,

View File

@@ -0,0 +1,118 @@
import type {
AddContactParams,
ContactResult,
SendGridContactObject,
SendGridContactRequest,
} from '@/tools/sendgrid/types'
import type { ToolConfig } from '@/tools/types'
export const sendGridAddContactTool: ToolConfig<AddContactParams, ContactResult> = {
id: 'sendgrid_add_contact',
name: 'SendGrid Add Contact',
description: 'Add a new contact to SendGrid',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'SendGrid API key',
},
email: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Contact email address',
},
firstName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Contact first name',
},
lastName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Contact last name',
},
customFields: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description:
'JSON object of custom field key-value pairs (use field IDs like e1_T, e2_N, e3_D, not field names)',
},
listIds: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated list IDs to add the contact to',
},
},
request: {
url: () => 'https://api.sendgrid.com/v3/marketing/contacts',
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const contact: SendGridContactObject = {
email: params.email,
}
if (params.firstName) contact.first_name = params.firstName
if (params.lastName) contact.last_name = params.lastName
if (params.customFields) {
const customFields =
typeof params.customFields === 'string'
? JSON.parse(params.customFields)
: params.customFields
Object.assign(contact, customFields)
}
const body: SendGridContactRequest = {
contacts: [contact],
}
if (params.listIds) {
body.list_ids = params.listIds.split(',').map((id) => id.trim())
}
return { body: JSON.stringify(body) }
},
},
transformResponse: async (response, params): Promise<ContactResult> => {
if (!response.ok) {
const error = await response.json()
throw new Error(error.errors?.[0]?.message || 'Failed to add contact')
}
const data = await response.json()
return {
success: true,
output: {
jobId: data.job_id,
email: params?.email || '',
firstName: params?.firstName,
lastName: params?.lastName,
message:
'Contact is being added. This is an asynchronous operation. Use the job ID to track status.',
},
}
},
outputs: {
jobId: { type: 'string', description: 'Job ID for tracking the async contact creation' },
email: { type: 'string', description: 'Contact email address' },
firstName: { type: 'string', description: 'Contact first name' },
lastName: { type: 'string', description: 'Contact last name' },
message: { type: 'string', description: 'Status message' },
},
}

View File

@@ -0,0 +1,74 @@
import type { AddContactsToListParams, SendGridContactObject } from '@/tools/sendgrid/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
export const sendGridAddContactsToListTool: ToolConfig<AddContactsToListParams, ToolResponse> = {
id: 'sendgrid_add_contacts_to_list',
name: 'SendGrid Add Contacts to List',
description:
'Add or update contacts and assign them to a list in SendGrid (uses PUT /v3/marketing/contacts)',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'SendGrid API key',
},
listId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'List ID to add contacts to',
},
contacts: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description:
'JSON array of contact objects. Each contact must have at least: email (or phone_number_id/external_id/anonymous_id). Example: [{"email": "user@example.com", "first_name": "John"}]',
},
},
request: {
url: () => 'https://api.sendgrid.com/v3/marketing/contacts',
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const contactsArray: SendGridContactObject[] =
typeof params.contacts === 'string' ? JSON.parse(params.contacts) : params.contacts
return {
body: JSON.stringify({
list_ids: [params.listId],
contacts: contactsArray,
}),
}
},
},
transformResponse: async (response): Promise<ToolResponse> => {
if (!response.ok) {
const error = (await response.json()) as { errors?: Array<{ message?: string }> }
throw new Error(error.errors?.[0]?.message || 'Failed to add contacts to list')
}
const data = (await response.json()) as { job_id: string }
return {
success: true,
output: {
jobId: data.job_id,
message: 'Contacts are being added to the list. This is an asynchronous operation.',
},
}
},
outputs: {
jobId: { type: 'string', description: 'Job ID for tracking the async operation' },
message: { type: 'string', description: 'Status message' },
},
}

View File

@@ -0,0 +1,64 @@
import type { CreateListParams, ListResult, SendGridList } from '@/tools/sendgrid/types'
import type { ToolConfig } from '@/tools/types'
export const sendGridCreateListTool: ToolConfig<CreateListParams, ListResult> = {
id: 'sendgrid_create_list',
name: 'SendGrid Create List',
description: 'Create a new contact list in SendGrid',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'SendGrid API key',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'List name',
},
},
request: {
url: () => 'https://api.sendgrid.com/v3/marketing/lists',
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
}),
body: (params) => {
return {
body: JSON.stringify({
name: params.name,
}),
}
},
},
transformResponse: async (response): Promise<ListResult> => {
if (!response.ok) {
const error = (await response.json()) as { errors?: Array<{ message?: string }> }
throw new Error(error.errors?.[0]?.message || 'Failed to create list')
}
const data = (await response.json()) as SendGridList
return {
success: true,
output: {
id: data.id,
name: data.name,
contactCount: data.contact_count,
},
}
},
outputs: {
id: { type: 'string', description: 'List ID' },
name: { type: 'string', description: 'List name' },
contactCount: { type: 'number', description: 'Number of contacts in the list' },
},
}

View File

@@ -0,0 +1,75 @@
import type { CreateTemplateParams, SendGridTemplate, TemplateResult } from '@/tools/sendgrid/types'
import type { ToolConfig } from '@/tools/types'
export const sendGridCreateTemplateTool: ToolConfig<CreateTemplateParams, TemplateResult> = {
id: 'sendgrid_create_template',
name: 'SendGrid Create Template',
description: 'Create a new email template in SendGrid',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'SendGrid API key',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Template name',
},
generation: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Template generation type (legacy or dynamic, default: dynamic)',
},
},
request: {
url: () => 'https://api.sendgrid.com/v3/templates',
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
}),
body: (params) => {
return {
body: JSON.stringify({
name: params.name,
generation: params.generation || 'dynamic',
}),
}
},
},
transformResponse: async (response): Promise<TemplateResult> => {
if (!response.ok) {
const error = (await response.json()) as { errors?: Array<{ message?: string }> }
throw new Error(error.errors?.[0]?.message || 'Failed to create template')
}
const data = (await response.json()) as SendGridTemplate
return {
success: true,
output: {
id: data.id,
name: data.name,
generation: data.generation,
updatedAt: data.updated_at,
versions: data.versions || [],
},
}
},
outputs: {
id: { type: 'string', description: 'Template ID' },
name: { type: 'string', description: 'Template name' },
generation: { type: 'string', description: 'Template generation' },
updatedAt: { type: 'string', description: 'Last update timestamp' },
versions: { type: 'json', description: 'Array of template versions' },
},
}

View File

@@ -0,0 +1,121 @@
import type {
CreateTemplateVersionParams,
SendGridTemplateVersionRequest,
TemplateVersionResult,
} from '@/tools/sendgrid/types'
import type { ToolConfig } from '@/tools/types'
export const sendGridCreateTemplateVersionTool: ToolConfig<
CreateTemplateVersionParams,
TemplateVersionResult
> = {
id: 'sendgrid_create_template_version',
name: 'SendGrid Create Template Version',
description: 'Create a new version of an email template in SendGrid',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'SendGrid API key',
},
templateId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Template ID',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Version name',
},
subject: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Email subject line',
},
htmlContent: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'HTML content of the template',
},
plainContent: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Plain text content of the template',
},
active: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether this version is active (default: true)',
},
},
request: {
url: (params) => `https://api.sendgrid.com/v3/templates/${params.templateId}/versions`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const body: SendGridTemplateVersionRequest = {
name: params.name,
subject: params.subject,
active: params.active !== undefined ? params.active : 1,
}
if (params.htmlContent) {
body.html_content = params.htmlContent
}
if (params.plainContent) {
body.plain_content = params.plainContent
}
return { body: JSON.stringify(body) }
},
},
transformResponse: async (response): Promise<TemplateVersionResult> => {
if (!response.ok) {
const error = await response.json()
throw new Error(error.errors?.[0]?.message || 'Failed to create template version')
}
const data = await response.json()
return {
success: true,
output: {
id: data.id,
templateId: data.template_id,
name: data.name,
subject: data.subject,
active: data.active === 1,
htmlContent: data.html_content,
plainContent: data.plain_content,
updatedAt: data.updated_at,
},
}
},
outputs: {
id: { type: 'string', description: 'Version ID' },
templateId: { type: 'string', description: 'Template ID' },
name: { type: 'string', description: 'Version name' },
subject: { type: 'string', description: 'Email subject' },
active: { type: 'boolean', description: 'Whether this version is active' },
htmlContent: { type: 'string', description: 'HTML content' },
plainContent: { type: 'string', description: 'Plain text content' },
updatedAt: { type: 'string', description: 'Last update timestamp' },
},
}

View File

@@ -0,0 +1,58 @@
import type { DeleteContactParams } from '@/tools/sendgrid/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
export const sendGridDeleteContactsTool: ToolConfig<DeleteContactParams, ToolResponse> = {
id: 'sendgrid_delete_contacts',
name: 'SendGrid Delete Contacts',
description: 'Delete one or more contacts from SendGrid',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'SendGrid API key',
},
contactIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated contact IDs to delete',
},
},
request: {
url: (params) => {
const ids = params.contactIds
.split(',')
.map((id) => id.trim())
.join(',')
return `https://api.sendgrid.com/v3/marketing/contacts?ids=${encodeURIComponent(ids)}`
},
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response): Promise<ToolResponse> => {
if (!response.ok) {
const error = (await response.json()) as { errors?: Array<{ message?: string }> }
throw new Error(error.errors?.[0]?.message || 'Failed to delete contacts')
}
const data = (await response.json()) as { job_id: string }
return {
success: true,
output: {
jobId: data.job_id,
},
}
},
outputs: {
jobId: { type: 'string', description: 'Job ID for the deletion request' },
},
}

View File

@@ -0,0 +1,51 @@
import type { DeleteListParams } from '@/tools/sendgrid/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
export const sendGridDeleteListTool: ToolConfig<DeleteListParams, ToolResponse> = {
id: 'sendgrid_delete_list',
name: 'SendGrid Delete List',
description: 'Delete a contact list from SendGrid',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'SendGrid API key',
},
listId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'List ID to delete',
},
},
request: {
url: (params) => `https://api.sendgrid.com/v3/marketing/lists/${params.listId}`,
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response): Promise<ToolResponse> => {
if (!response.ok) {
const error = (await response.json()) as { errors?: Array<{ message?: string }> }
throw new Error(error.errors?.[0]?.message || 'Failed to delete list')
}
// API returns 204 No Content on success
return {
success: true,
output: {
message: 'List deleted successfully',
},
}
},
outputs: {
message: { type: 'string', description: 'Success message' },
},
}

View File

@@ -0,0 +1,46 @@
import type { DeleteTemplateParams } from '@/tools/sendgrid/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
export const sendGridDeleteTemplateTool: ToolConfig<DeleteTemplateParams, ToolResponse> = {
id: 'sendgrid_delete_template',
name: 'SendGrid Delete Template',
description: 'Delete an email template from SendGrid',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'SendGrid API key',
},
templateId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Template ID to delete',
},
},
request: {
url: (params) => `https://api.sendgrid.com/v3/templates/${params.templateId}`,
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response): Promise<ToolResponse> => {
if (!response.ok) {
const error = (await response.json()) as { errors?: Array<{ message?: string }> }
throw new Error(error.errors?.[0]?.message || 'Failed to delete template')
}
return {
success: true,
output: {},
}
},
outputs: {},
}

View File

@@ -0,0 +1,66 @@
import type { ContactResult, GetContactParams, SendGridContact } from '@/tools/sendgrid/types'
import type { ToolConfig } from '@/tools/types'
export const sendGridGetContactTool: ToolConfig<GetContactParams, ContactResult> = {
id: 'sendgrid_get_contact',
name: 'SendGrid Get Contact',
description: 'Get a specific contact by ID from SendGrid',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'SendGrid API key',
},
contactId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Contact ID',
},
},
request: {
url: (params) => `https://api.sendgrid.com/v3/marketing/contacts/${params.contactId}`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response): Promise<ContactResult> => {
if (!response.ok) {
const error = (await response.json()) as { errors?: Array<{ message?: string }> }
throw new Error(error.errors?.[0]?.message || 'Failed to get contact')
}
const data = (await response.json()) as SendGridContact
return {
success: true,
output: {
id: data.id,
email: data.email,
firstName: data.first_name,
lastName: data.last_name,
createdAt: data.created_at,
updatedAt: data.updated_at,
listIds: data.list_ids,
customFields: data.custom_fields,
},
}
},
outputs: {
id: { type: 'string', description: 'Contact ID' },
email: { type: 'string', description: 'Contact email address' },
firstName: { type: 'string', description: 'Contact first name' },
lastName: { type: 'string', description: 'Contact last name' },
createdAt: { type: 'string', description: 'Creation timestamp' },
updatedAt: { type: 'string', description: 'Last update timestamp' },
listIds: { type: 'json', description: 'Array of list IDs the contact belongs to' },
customFields: { type: 'json', description: 'Custom field values' },
},
}

View File

@@ -0,0 +1,56 @@
import type { GetListParams, ListResult, SendGridList } from '@/tools/sendgrid/types'
import type { ToolConfig } from '@/tools/types'
export const sendGridGetListTool: ToolConfig<GetListParams, ListResult> = {
id: 'sendgrid_get_list',
name: 'SendGrid Get List',
description: 'Get a specific list by ID from SendGrid',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'SendGrid API key',
},
listId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'List ID',
},
},
request: {
url: (params) => `https://api.sendgrid.com/v3/marketing/lists/${params.listId}`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response): Promise<ListResult> => {
if (!response.ok) {
const error = (await response.json()) as { errors?: Array<{ message?: string }> }
throw new Error(error.errors?.[0]?.message || 'Failed to get list')
}
const data = (await response.json()) as SendGridList
return {
success: true,
output: {
id: data.id,
name: data.name,
contactCount: data.contact_count,
},
}
},
outputs: {
id: { type: 'string', description: 'List ID' },
name: { type: 'string', description: 'List name' },
contactCount: { type: 'number', description: 'Number of contacts in the list' },
},
}

View File

@@ -0,0 +1,60 @@
import type { GetTemplateParams, SendGridTemplate, TemplateResult } from '@/tools/sendgrid/types'
import type { ToolConfig } from '@/tools/types'
export const sendGridGetTemplateTool: ToolConfig<GetTemplateParams, TemplateResult> = {
id: 'sendgrid_get_template',
name: 'SendGrid Get Template',
description: 'Get a specific template by ID from SendGrid',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'SendGrid API key',
},
templateId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Template ID',
},
},
request: {
url: (params) => `https://api.sendgrid.com/v3/templates/${params.templateId}`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response): Promise<TemplateResult> => {
if (!response.ok) {
const error = (await response.json()) as { errors?: Array<{ message?: string }> }
throw new Error(error.errors?.[0]?.message || 'Failed to get template')
}
const data = (await response.json()) as SendGridTemplate
return {
success: true,
output: {
id: data.id,
name: data.name,
generation: data.generation,
updatedAt: data.updated_at,
versions: data.versions || [],
},
}
},
outputs: {
id: { type: 'string', description: 'Template ID' },
name: { type: 'string', description: 'Template name' },
generation: { type: 'string', description: 'Template generation' },
updatedAt: { type: 'string', description: 'Last update timestamp' },
versions: { type: 'json', description: 'Array of template versions' },
},
}

View File

@@ -0,0 +1,23 @@
// Mail Send
// Contact Management
export { sendGridAddContactTool } from './add_contact'
export { sendGridAddContactsToListTool } from './add_contacts_to_list'
// List Management
export { sendGridCreateListTool } from './create_list'
// Template Management
export { sendGridCreateTemplateTool } from './create_template'
export { sendGridCreateTemplateVersionTool } from './create_template_version'
export { sendGridDeleteContactsTool } from './delete_contacts'
export { sendGridDeleteListTool } from './delete_list'
export { sendGridDeleteTemplateTool } from './delete_template'
export { sendGridGetContactTool } from './get_contact'
export { sendGridGetListTool } from './get_list'
export { sendGridGetTemplateTool } from './get_template'
export { sendGridListAllListsTool } from './list_all_lists'
export { sendGridListTemplatesTool } from './list_templates'
export { sendGridRemoveContactsFromListTool } from './remove_contacts_from_list'
export { sendGridSearchContactsTool } from './search_contacts'
export { sendGridSendMailTool } from './send_mail'
// Types
export * from './types'

View File

@@ -0,0 +1,58 @@
import type { ListAllListsParams, ListsResult, SendGridList } from '@/tools/sendgrid/types'
import type { ToolConfig } from '@/tools/types'
export const sendGridListAllListsTool: ToolConfig<ListAllListsParams, ListsResult> = {
id: 'sendgrid_list_all_lists',
name: 'SendGrid List All Lists',
description: 'Get all contact lists from SendGrid',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'SendGrid API key',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of lists to return per page (default: 100)',
},
},
request: {
url: (params) => {
const url = new URL('https://api.sendgrid.com/v3/marketing/lists')
if (params.pageSize) {
url.searchParams.append('page_size', params.pageSize.toString())
}
return url.toString()
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response): Promise<ListsResult> => {
if (!response.ok) {
const error = (await response.json()) as { errors?: Array<{ message?: string }> }
throw new Error(error.errors?.[0]?.message || 'Failed to list all lists')
}
const data = (await response.json()) as { result?: SendGridList[] }
return {
success: true,
output: {
lists: data.result || [],
},
}
},
outputs: {
lists: { type: 'json', description: 'Array of lists' },
},
}

View File

@@ -0,0 +1,70 @@
import type { ListTemplatesParams, SendGridTemplate, TemplatesResult } from '@/tools/sendgrid/types'
import type { ToolConfig } from '@/tools/types'
export const sendGridListTemplatesTool: ToolConfig<ListTemplatesParams, TemplatesResult> = {
id: 'sendgrid_list_templates',
name: 'SendGrid List Templates',
description: 'Get all email templates from SendGrid',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'SendGrid API key',
},
generations: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by generation (legacy, dynamic, or both)',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of templates to return per page (default: 20)',
},
},
request: {
url: (params) => {
const url = new URL('https://api.sendgrid.com/v3/templates')
if (params.generations) {
url.searchParams.append('generations', params.generations)
}
if (params.pageSize) {
url.searchParams.append('page_size', params.pageSize.toString())
}
return url.toString()
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response): Promise<TemplatesResult> => {
if (!response.ok) {
const error = (await response.json()) as { errors?: Array<{ message?: string }> }
throw new Error(error.errors?.[0]?.message || 'Failed to list templates')
}
const data = (await response.json()) as {
result?: SendGridTemplate[]
templates?: SendGridTemplate[]
}
return {
success: true,
output: {
templates: data.result || data.templates || [],
},
}
},
outputs: {
templates: { type: 'json', description: 'Array of templates' },
},
}

View File

@@ -0,0 +1,67 @@
import type { RemoveContactsFromListParams } from '@/tools/sendgrid/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
export const sendGridRemoveContactsFromListTool: ToolConfig<
RemoveContactsFromListParams,
ToolResponse
> = {
id: 'sendgrid_remove_contacts_from_list',
name: 'SendGrid Remove Contacts from List',
description: 'Remove contacts from a specific list in SendGrid',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'SendGrid API key',
},
listId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'List ID',
},
contactIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated contact IDs to remove from the list',
},
},
request: {
url: (params) => {
const contactIds = params.contactIds
.split(',')
.map((id) => id.trim())
.join(',')
return `https://api.sendgrid.com/v3/marketing/lists/${params.listId}/contacts?contact_ids=${encodeURIComponent(contactIds)}`
},
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response): Promise<ToolResponse> => {
if (!response.ok) {
const error = (await response.json()) as { errors?: Array<{ message?: string }> }
throw new Error(error.errors?.[0]?.message || 'Failed to remove contacts from list')
}
const data = (await response.json()) as { job_id?: string }
return {
success: true,
output: {
jobId: data.job_id,
},
}
},
outputs: {
jobId: { type: 'string', description: 'Job ID for the request' },
},
}

View File

@@ -0,0 +1,66 @@
import type { ContactsResult, SearchContactsParams, SendGridContact } from '@/tools/sendgrid/types'
import type { ToolConfig } from '@/tools/types'
export const sendGridSearchContactsTool: ToolConfig<SearchContactsParams, ContactsResult> = {
id: 'sendgrid_search_contacts',
name: 'SendGrid Search Contacts',
description: 'Search for contacts in SendGrid using a query',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'SendGrid API key',
},
query: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
"Search query (e.g., \"email LIKE '%example.com%' AND CONTAINS(list_ids, 'list-id')\")",
},
},
request: {
url: () => 'https://api.sendgrid.com/v3/marketing/contacts/search',
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
}),
body: (params) => {
return {
body: JSON.stringify({
query: params.query,
}),
}
},
},
transformResponse: async (response): Promise<ContactsResult> => {
if (!response.ok) {
const error = (await response.json()) as { errors?: Array<{ message?: string }> }
throw new Error(error.errors?.[0]?.message || 'Failed to search contacts')
}
const data = (await response.json()) as {
result?: SendGridContact[]
contact_count?: number
}
return {
success: true,
output: {
contacts: data.result || [],
contactCount: data.contact_count,
},
}
},
outputs: {
contacts: { type: 'json', description: 'Array of matching contacts' },
contactCount: { type: 'number', description: 'Total number of contacts found' },
},
}

Some files were not shown because too many files have changed in this diff Show More