diff --git a/src/components/ConfigurationWizard.tsx b/src/components/ConfigurationWizard.tsx index b21261e..a3d06db 100644 --- a/src/components/ConfigurationWizard.tsx +++ b/src/components/ConfigurationWizard.tsx @@ -50,6 +50,7 @@ import { EmbeddingWizard } from "./wizards/EmbeddingWizard"; import { SimilaritySearchWizard } from "./wizards/SimilaritySearchWizard"; import { StructuredOutputWizard } from "./wizards/StructuredOutputWizard"; import { DiscordWizard } from "./wizards/DiscordWizard"; +import { GmailWizard } from "./wizards/GmailWizard"; const WIZARD_STEPS: Record = { dataInput: [ @@ -158,6 +159,17 @@ const WIZARD_STEPS: Record = { helpText: "Set up your Discord operation parameters", }, ], + // Gmail integration + gmail: [ + { + id: "gmail-config", + title: "Gmail Integration", + description: "Configure your Gmail integration", + component: GmailWizard, + validation: (data: any) => !!data.operation && !!data.accessToken, + helpText: "Set up your Gmail operation parameters", + }, + ], }; export const ConfigurationWizard: React.FC = ({ @@ -221,6 +233,7 @@ export const ConfigurationWizard: React.FC = ({ database: "Database Operations", slack: "Slack Integration", discord: "Discord Integration", + gmail: "Gmail Integration", }; return labels[type] || "New Node"; }; diff --git a/src/components/NodeLibrary.tsx b/src/components/NodeLibrary.tsx index 89da82d..9710236 100644 --- a/src/components/NodeLibrary.tsx +++ b/src/components/NodeLibrary.tsx @@ -43,6 +43,7 @@ export const NodeLibrary: React.FC = () => { "database", "slack", "discord", + "gmail", ]; const nodeTypeColors: Record = { @@ -56,6 +57,7 @@ export const NodeLibrary: React.FC = () => { database: "cyan", slack: "purple", discord: "indigo", + gmail: "red", }; const nodeTypesByCategory = getNodeTypesByCategory(); diff --git a/src/components/WorkflowEditor.tsx b/src/components/WorkflowEditor.tsx index 5452b9e..6931b4d 100644 --- a/src/components/WorkflowEditor.tsx +++ b/src/components/WorkflowEditor.tsx @@ -36,6 +36,8 @@ import { SlackNode, // Discord node DiscordNode, + // Gmail node + GmailNode, } from "./nodes"; import { NodeData } from "../types"; import { Grid, Trash2, Sparkles, X, Lightbulb, Play } from "lucide-react"; @@ -54,6 +56,8 @@ const nodeTypes: NodeTypes = { slack: SlackNode, // Discord node discord: DiscordNode, + // Gmail node + gmail: GmailNode, }; interface WorkflowEditorProps { diff --git a/src/components/nodes/BaseNode.tsx b/src/components/nodes/BaseNode.tsx index ef68aea..ca28f32 100644 --- a/src/components/nodes/BaseNode.tsx +++ b/src/components/nodes/BaseNode.tsx @@ -15,6 +15,7 @@ import { Database, MessageSquare, Bot, + Mail, } from "lucide-react"; import { clsx } from "clsx"; @@ -29,6 +30,7 @@ const nodeIcons: Record> = { database: Database, slack: MessageSquare, discord: Bot, + gmail: Mail, }; const nodeColors: Record< @@ -95,6 +97,12 @@ const nodeColors: Record< icon: "text-indigo-600", accent: "bg-indigo-500", }, + gmail: { + bg: "bg-red-50", + border: "border-red-200", + icon: "text-red-600", + accent: "bg-red-500", + }, }; const statusConfig = { diff --git a/src/components/nodes/GmailNode.tsx b/src/components/nodes/GmailNode.tsx new file mode 100644 index 0000000..858a1d3 --- /dev/null +++ b/src/components/nodes/GmailNode.tsx @@ -0,0 +1,185 @@ +import React from "react"; +import { NodeData } from "../../types"; +import { GmailOperation } from "../../types/gmail"; +import { NodeProps } from "reactflow"; +import { + Mail, + Send, + Eye, + Reply, + Forward, + FileText, + Tag, + Search, + Paperclip, + CheckCircle, + XCircle, + Clock, +} from "lucide-react"; + +interface GmailNodeProps extends NodeProps { + data: NodeData; +} + +const operationIcons: Record> = { + [GmailOperation.SEND]: Send, + [GmailOperation.READ]: Eye, + [GmailOperation.REPLY]: Reply, + [GmailOperation.FORWARD]: Forward, + [GmailOperation.DRAFT]: FileText, + [GmailOperation.LABEL]: Tag, + [GmailOperation.SEARCH]: Search, + [GmailOperation.ATTACHMENT]: Paperclip, +}; + +const operationLabels: Record = { + [GmailOperation.SEND]: "Send Email", + [GmailOperation.READ]: "Read Email", + [GmailOperation.REPLY]: "Reply to Email", + [GmailOperation.FORWARD]: "Forward Email", + [GmailOperation.DRAFT]: "Manage Draft", + [GmailOperation.LABEL]: "Manage Label", + [GmailOperation.SEARCH]: "Search Emails", + [GmailOperation.ATTACHMENT]: "Handle Attachment", +}; + +export const GmailNode: React.FC = ({ data, selected }) => { + const operation = data.config?.operation as GmailOperation; + const IconComponent = operation ? operationIcons[operation] : Mail; + const operationLabel = operation + ? operationLabels[operation] + : "Gmail Operation"; + + const getStatusColor = () => { + if (data.status === "success") return "text-green-600"; + if (data.status === "error") return "text-red-600"; + if (data.status === "running") return "text-blue-600"; + return "text-gray-600"; + }; + + const getPreviewData = () => { + if (!data.config) return null; + + switch (operation) { + case GmailOperation.SEND: + return { + to: data.config.to || "No recipient", + subject: data.config.subject || "No subject", + }; + case GmailOperation.READ: + return { + messageId: data.config.messageId || "No message ID", + query: data.config.query || "No query", + }; + case GmailOperation.REPLY: + return { + replyTo: data.config.replyToMessageId || "No reply target", + subject: data.config.subject || "No subject", + }; + case GmailOperation.FORWARD: + return { + messageId: data.config.messageId || "No message ID", + to: data.config.to || "No recipient", + }; + case GmailOperation.DRAFT: + return { + draftId: data.config.draftId || "New draft", + subject: data.config.subject || "No subject", + }; + case GmailOperation.LABEL: + return { + labelName: data.config.labelName || "No label name", + labelId: data.config.labelId || "No label ID", + }; + case GmailOperation.SEARCH: + return { + query: data.config.searchQuery || "No search query", + maxResults: data.config.maxResults || "Default", + }; + case GmailOperation.ATTACHMENT: + return { + messageId: data.config.messageId || "No message ID", + attachmentId: data.config.attachmentId || "No attachment ID", + }; + default: + return null; + } + }; + + const preview = getPreviewData(); + + return ( +
+ {/* Header */} +
+
+
+ +
+
+

+ {data.label || "Gmail Integration"} +

+

{operationLabel}

+
+
+ + {/* Status Indicator */} +
+ {data.status === "running" && ( +
+ +
+ )} + {data.status === "success" && ( +
+ +
+ )} + {data.status === "error" && ( +
+ +
+ )} +
+
+ + {/* Content */} +
+
+ {/* Operation Details */} +
+ {preview && ( +
+ {Object.entries(preview).map(([key, value]) => ( +
+ {key}: + + {String(value)} + +
+ ))} +
+ )} +
+ + {/* Status Display */} + {data.status && ( +
+ {data.status === "success" && "✓ Completed"} + {data.status === "error" && "✗ Failed"} + {data.status === "running" && "⏳ Running"} + {data.status === "idle" && "⏸ Ready"} +
+ )} +
+
+
+ ); +}; diff --git a/src/components/nodes/index.ts b/src/components/nodes/index.ts index 11f5586..c1bce79 100644 --- a/src/components/nodes/index.ts +++ b/src/components/nodes/index.ts @@ -12,3 +12,5 @@ export { UnifiedDatabaseNode } from "./UnifiedDatabaseNode"; export { SlackNode } from "./SlackNode"; // Discord node export { DiscordNode } from "./DiscordNode"; +// Gmail node +export { GmailNode } from "./GmailNode"; diff --git a/src/components/wizards/GmailWizard.tsx b/src/components/wizards/GmailWizard.tsx new file mode 100644 index 0000000..6513632 --- /dev/null +++ b/src/components/wizards/GmailWizard.tsx @@ -0,0 +1,211 @@ +import React, { useState } from "react"; +import { WizardStepProps } from "../ConfigurationWizard"; +import { Button } from "../ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"; +import { Badge } from "../ui/badge"; +import { + Mail, + Send, + Eye, + Reply, + Forward, + FileText, + Tag, + Search, + Paperclip, + CheckCircle, + Settings, +} from "lucide-react"; +import { GmailOperation } from "../../types/gmail"; + +const GMAIL_OPERATIONS = [ + { + id: GmailOperation.SEND, + name: "Send Email", + description: "Send emails with attachments and rich content", + icon: Send, + color: "bg-blue-100 text-blue-800 border-blue-200", + features: ["Rich HTML", "Attachments", "CC/BCC", "Threading"], + }, + { + id: GmailOperation.READ, + name: "Read Email", + description: "Read emails and message threads", + icon: Eye, + color: "bg-green-100 text-green-800 border-green-200", + features: ["Message content", "Headers", "Attachments", "Thread view"], + }, + { + id: GmailOperation.REPLY, + name: "Reply to Email", + description: "Reply to specific email messages", + icon: Reply, + color: "bg-purple-100 text-purple-800 border-purple-200", + features: ["Thread replies", "Rich content", "Attachments", "Auto-reply"], + }, + { + id: GmailOperation.FORWARD, + name: "Forward Email", + description: "Forward emails to other recipients", + icon: Forward, + color: "bg-orange-100 text-orange-800 border-orange-200", + features: [ + "Multiple recipients", + "Add comments", + "Preserve formatting", + "Thread context", + ], + }, + { + id: GmailOperation.DRAFT, + name: "Manage Draft", + description: "Create, edit, and manage email drafts", + icon: FileText, + color: "bg-yellow-100 text-yellow-800 border-yellow-200", + features: ["Save drafts", "Edit drafts", "Send drafts", "Draft templates"], + }, + { + id: GmailOperation.LABEL, + name: "Manage Label", + description: "Create and manage Gmail labels", + icon: Tag, + color: "bg-pink-100 text-pink-800 border-pink-200", + features: [ + "Create labels", + "Color coding", + "Nested labels", + "Auto-labeling", + ], + }, + { + id: GmailOperation.SEARCH, + name: "Search Emails", + description: "Search emails with advanced filters", + icon: Search, + color: "bg-indigo-100 text-indigo-800 border-indigo-200", + features: ["Advanced search", "Filters", "Date ranges", "Custom queries"], + }, + { + id: GmailOperation.ATTACHMENT, + name: "Handle Attachment", + description: "Download and manage email attachments", + icon: Paperclip, + color: "bg-teal-100 text-teal-800 border-teal-200", + features: [ + "Download files", + "File info", + "Multiple formats", + "Batch processing", + ], + }, +]; + +export const GmailWizard: React.FC = ({ + data, + onChange, + onNext, + onPrevious, +}) => { + const [selectedOperation, setSelectedOperation] = useState( + data.operation || GmailOperation.SEND + ); + + const handleOperationSelect = (operation: GmailOperation) => { + setSelectedOperation(operation); + onChange({ + ...data, + operation, + }); + }; + + const handleNext = () => { + if (selectedOperation) { + onNext(); + } + }; + + return ( +
+
+
+ +
+

Gmail Integration

+

+ Choose what type of Gmail operation you want to perform +

+
+ +
+ {GMAIL_OPERATIONS.map((operation) => { + const IconComponent = operation.icon; + const isSelected = selectedOperation === operation.id; + + return ( + handleOperationSelect(operation.id)} + > + +
+
+ +
+
+ + {operation.name} + +

+ {operation.description} +

+
+ {isSelected && ( + + )} +
+
+ +
+ {operation.features.map((feature, index) => ( + + {feature} + + ))} +
+
+
+ ); + })} +
+ +
+ + + +
+
+ ); +}; diff --git a/src/config/fieldConfigs.ts b/src/config/fieldConfigs.ts index 97b9e21..09e7afa 100644 --- a/src/config/fieldConfigs.ts +++ b/src/config/fieldConfigs.ts @@ -388,6 +388,42 @@ export const fieldConfigs: Record = { maxLength: 50, }, }, + + // Gmail-specific fields + "gmail.accessToken": { + technicalName: "accessToken", + userFriendlyName: "Gmail Access Token", + description: "Your Gmail API access token for authentication", + helpText: + "OAuth2 access token for Gmail API. This allows the workflow to access your Gmail account.", + examples: [ + "ya29.a0AfH6SMC... (OAuth2 token)", + "Obtain from Google Cloud Console", + ], + validation: { + required: true, + }, + }, + "gmail.operation": { + technicalName: "operation", + userFriendlyName: "Gmail operation", + description: "Choose what type of Gmail operation to perform", + helpText: + "Select the type of Gmail operation you want to perform. Each operation has different configuration options.", + examples: [ + "Send - Send emails with attachments", + "Read - Read emails and threads", + "Reply - Reply to specific messages", + "Forward - Forward emails", + "Draft - Create and manage drafts", + "Label - Manage labels and folders", + "Search - Search emails with filters", + "Attachment - Handle file attachments", + ], + validation: { + required: true, + }, + }, }; export const getFieldConfig = ( @@ -426,6 +462,7 @@ export const getFieldsForNodeType = (nodeType: string): string[] => { database: ["connectionId", "operation", "label"], slack: ["botToken", "operation", "label"], discord: ["botToken", "operation", "label"], + gmail: ["accessToken", "operation", "label"], }; return fieldMappings[nodeType] || []; diff --git a/src/config/nodeTypeConfigs.ts b/src/config/nodeTypeConfigs.ts index a877c2a..6a55f88 100644 --- a/src/config/nodeTypeConfigs.ts +++ b/src/config/nodeTypeConfigs.ts @@ -171,6 +171,28 @@ export const nodeTypeConfigs: Record = { "Welcome new members", ], }, + gmail: { + technicalName: "gmail", + userFriendlyName: "Gmail Integration", + description: + "Send, read, manage emails, labels, drafts, and search Gmail messages", + category: "Communication", + icon: "📧", + helpText: + "Integrate with Gmail to send emails, read messages, manage labels, create drafts, and search your inbox. Perfect for email automation and communication workflows.", + commonUseCases: [ + "Send automated emails", + "Read and process incoming emails", + "Reply to emails automatically", + "Forward important messages", + "Create and manage email drafts", + "Organize emails with labels", + "Search emails with filters", + "Handle email attachments", + "Email marketing campaigns", + "Customer support automation", + ], + }, }; export const getNodeTypeConfig = (nodeType: NodeType): NodeTypeConfig => { diff --git a/src/services/executionEngine.ts b/src/services/executionEngine.ts index 9c1b4e8..dc6d3e5 100644 --- a/src/services/executionEngine.ts +++ b/src/services/executionEngine.ts @@ -836,6 +836,8 @@ export class ExecutionEngine { return (await import("./processors/slackProcessor")).default; case "discord": return (await import("./processors/discordProcessor")).default; + case "gmail": + return (await import("./processors/gmailProcessor")).default; case "structuredOutput": return (await import("./processors/structuredOutputProcessor")).default; case "embeddingGenerator": diff --git a/src/services/gmail/GmailService.ts b/src/services/gmail/GmailService.ts new file mode 100644 index 0000000..41def72 --- /dev/null +++ b/src/services/gmail/GmailService.ts @@ -0,0 +1,302 @@ +import { GmailOperation, GmailConfig } from "../../types/gmail"; + +export class GmailService { + private baseUrl = "https://gmail.googleapis.com/gmail/v1"; + + async executeOperation( + operation: GmailOperation, + config: GmailConfig + ): Promise { + try { + switch (operation) { + case GmailOperation.SEND: + return await this.sendEmail(config); + case GmailOperation.READ: + return await this.readEmail(config); + case GmailOperation.REPLY: + return await this.replyToEmail(config); + case GmailOperation.FORWARD: + return await this.forwardEmail(config); + case GmailOperation.DRAFT: + return await this.manageDraft(config); + case GmailOperation.LABEL: + return await this.manageLabel(config); + case GmailOperation.SEARCH: + return await this.searchEmails(config); + case GmailOperation.ATTACHMENT: + return await this.handleAttachment(config); + default: + throw new Error(`Unsupported Gmail operation: ${operation}`); + } + } catch (error) { + throw new Error( + `Gmail operation failed: ${ + error instanceof Error ? error.message : "Unknown error" + }` + ); + } + } + + private async sendEmail(config: GmailConfig): Promise { + const message = this.createMessage(config); + return await this.makeRequest( + "/users/me/messages/send", + { + method: "POST", + body: JSON.stringify({ raw: message }), + }, + config.accessToken + ); + } + + private async readEmail(config: GmailConfig): Promise { + if (config.messageId) { + return await this.makeRequest( + `/users/me/messages/${config.messageId}`, + { + method: "GET", + }, + config.accessToken + ); + } + + // List messages with query + const params = new URLSearchParams(); + if (config.query) params.append("q", config.query); + if (config.maxResults) + params.append("maxResults", config.maxResults.toString()); + + return await this.makeRequest( + `/users/me/messages?${params.toString()}`, + { + method: "GET", + }, + config.accessToken + ); + } + + private async replyToEmail(config: GmailConfig): Promise { + const message = this.createMessage(config); + return await this.makeRequest( + `/users/me/messages/${config.replyToMessageId}/reply`, + { + method: "POST", + body: JSON.stringify({ raw: message }), + }, + config.accessToken + ); + } + + private async forwardEmail(config: GmailConfig): Promise { + const message = this.createMessage(config); + return await this.makeRequest( + `/users/me/messages/${config.messageId}/forward`, + { + method: "POST", + body: JSON.stringify({ raw: message }), + }, + config.accessToken + ); + } + + private async manageDraft(config: GmailConfig): Promise { + if (config.draftId) { + // Get existing draft + return await this.makeRequest( + `/users/me/drafts/${config.draftId}`, + { + method: "GET", + }, + config.accessToken + ); + } + + // Create new draft + const message = this.createMessage(config); + return await this.makeRequest( + "/users/me/drafts", + { + method: "POST", + body: JSON.stringify({ + message: { raw: message }, + }), + }, + config.accessToken + ); + } + + private async manageLabel(config: GmailConfig): Promise { + if (config.labelId) { + // Get existing label + return await this.makeRequest( + `/users/me/labels/${config.labelId}`, + { + method: "GET", + }, + config.accessToken + ); + } + + // Create new label + const labelData = { + name: config.labelName, + labelListVisibility: "labelShow", + messageListVisibility: "show", + }; + + if (config.labelColor) { + labelData.color = { textColor: config.labelColor }; + } + + return await this.makeRequest( + "/users/me/labels", + { + method: "POST", + body: JSON.stringify(labelData), + }, + config.accessToken + ); + } + + private async searchEmails(config: GmailConfig): Promise { + const params = new URLSearchParams(); + if (config.searchQuery) params.append("q", config.searchQuery); + if (config.maxResults) + params.append("maxResults", config.maxResults.toString()); + if (config.includeSpamTrash) params.append("includeSpamTrash", "true"); + + return await this.makeRequest( + `/users/me/messages?${params.toString()}`, + { + method: "GET", + }, + config.accessToken + ); + } + + private async handleAttachment(config: GmailConfig): Promise { + return await this.makeRequest( + `/users/me/messages/${config.messageId}/attachments/${config.attachmentId}`, + { + method: "GET", + }, + config.accessToken + ); + } + + private createMessage(config: GmailConfig): string { + const headers = []; + + if (config.to) headers.push(`To: ${config.to}`); + if (config.cc) headers.push(`Cc: ${config.cc}`); + if (config.bcc) headers.push(`Bcc: ${config.bcc}`); + if (config.subject) headers.push(`Subject: ${config.subject}`); + + headers.push("Content-Type: text/html; charset=utf-8"); + headers.push(""); // Empty line before body + + const body = config.htmlBody || config.body || ""; + + const fullMessage = [...headers, body].join("\n"); + return Buffer.from(fullMessage).toString("base64url"); + } + + private async makeRequest( + endpoint: string, + options: RequestInit, + accessToken: string + ): Promise { + const url = `${this.baseUrl}${endpoint}`; + + const response = await fetch(url, { + ...options, + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + ...options.headers, + }, + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + `Gmail API error: ${response.status} ${response.statusText} - ${ + errorData.error?.message || "Unknown error" + }` + ); + } + + return await response.json(); + } + + validateConfig(config: GmailConfig): { + isValid: boolean; + errors: string[]; + } { + const errors: string[] = []; + + if (!config.accessToken) { + errors.push("Access token is required"); + } + + if (!config.operation) { + errors.push("Operation is required"); + } + + switch (config.operation) { + case GmailOperation.SEND: + if (!config.to) + errors.push("Recipient email is required for send operation"); + if (!config.subject) + errors.push("Subject is required for send operation"); + if (!config.body && !config.htmlBody) + errors.push("Message body is required for send operation"); + break; + case GmailOperation.READ: + if (!config.messageId && !config.query) { + errors.push( + "Either message ID or search query is required for read operation" + ); + } + break; + case GmailOperation.REPLY: + if (!config.replyToMessageId) + errors.push("Reply to message ID is required for reply operation"); + if (!config.body && !config.htmlBody) + errors.push("Reply message body is required"); + break; + case GmailOperation.FORWARD: + if (!config.messageId) + errors.push("Message ID is required for forward operation"); + if (!config.to) + errors.push("Recipient email is required for forward operation"); + break; + case GmailOperation.DRAFT: + if (!config.subject) + errors.push("Subject is required for draft operation"); + break; + case GmailOperation.LABEL: + if (!config.labelName && !config.labelId) { + errors.push( + "Either label name or label ID is required for label operation" + ); + } + break; + case GmailOperation.SEARCH: + if (!config.searchQuery) + errors.push("Search query is required for search operation"); + break; + case GmailOperation.ATTACHMENT: + if (!config.messageId) + errors.push("Message ID is required for attachment operation"); + if (!config.attachmentId) + errors.push("Attachment ID is required for attachment operation"); + break; + } + + return { + isValid: errors.length === 0, + errors, + }; + } +} diff --git a/src/services/processors/ProcessorRegistry.ts b/src/services/processors/ProcessorRegistry.ts index 218f581..aade35b 100644 --- a/src/services/processors/ProcessorRegistry.ts +++ b/src/services/processors/ProcessorRegistry.ts @@ -10,6 +10,7 @@ export class ProcessorRegistry { "database", "slack", "discord", + "gmail", "structuredOutput", "embeddingGenerator", "similaritySearch", @@ -155,6 +156,10 @@ export class ProcessorRegistry { pattern: /discord|gaming|community|server/i, types: ["discord"], }, + { + pattern: /gmail|email|mail/i, + types: ["gmail"], + }, { pattern: /embed|vector/i, types: ["embeddingGenerator", "similaritySearch"], diff --git a/src/services/processors/gmailProcessor.ts b/src/services/processors/gmailProcessor.ts new file mode 100644 index 0000000..eaee759 --- /dev/null +++ b/src/services/processors/gmailProcessor.ts @@ -0,0 +1,54 @@ +import { NodeData } from "../../types"; +import { GmailService } from "../gmail/GmailService"; +import { GmailOperation } from "../../types/gmail"; + +async function gmailProcessor(nodeData: NodeData): Promise<{ + success: boolean; + data?: any; + error?: string; +}> { + try { + const { config } = nodeData; + + if (!config) { + return { + success: false, + error: "Gmail configuration is missing", + }; + } + + const gmailService = new GmailService(); + + // Validate configuration + const validation = gmailService.validateConfig(config as any); + if (!validation.isValid) { + return { + success: false, + error: `Configuration validation failed: ${validation.errors.join( + ", " + )}`, + }; + } + + // Execute Gmail operation + const result = await gmailService.executeOperation( + config.operation as GmailOperation, + config as any + ); + + return { + success: true, + data: result, + }; + } catch (error) { + return { + success: false, + error: + error instanceof Error + ? error.message + : "Unknown Gmail operation error", + }; + } +} + +export default gmailProcessor; diff --git a/src/types/gmail.ts b/src/types/gmail.ts new file mode 100644 index 0000000..2b25bcc --- /dev/null +++ b/src/types/gmail.ts @@ -0,0 +1,50 @@ +export enum GmailOperation { + SEND = "send", + READ = "read", + REPLY = "reply", + FORWARD = "forward", + DRAFT = "draft", + LABEL = "label", + SEARCH = "search", + ATTACHMENT = "attachment", +} + +export interface GmailConfig { + accessToken: string; + operation: GmailOperation; + label: string; + + // Send operations + to?: string; + cc?: string; + bcc?: string; + subject?: string; + body?: string; + htmlBody?: string; + + // Read operations + messageId?: string; + query?: string; + maxResults?: number; + + // Reply/Forward operations + threadId?: string; + replyToMessageId?: string; + + // Draft operations + draftId?: string; + + // Label operations + labelName?: string; + labelId?: string; + labelColor?: string; + + // Search operations + searchQuery?: string; + includeSpamTrash?: boolean; + + // Attachment operations + attachmentId?: string; + filename?: string; + mimeType?: string; +} diff --git a/src/types/index.ts b/src/types/index.ts index 2be6f0f..4b5b5e3 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -21,7 +21,9 @@ export type NodeType = | "database" // Slack integration | "slack" - | "discord"; + | "discord" + // Gmail integration + | "gmail"; export type NodeStatus = | "idle" diff --git a/src/utils/connectionSuggestions.ts b/src/utils/connectionSuggestions.ts index 28a1c3d..0af7835 100644 --- a/src/utils/connectionSuggestions.ts +++ b/src/utils/connectionSuggestions.ts @@ -140,6 +140,18 @@ const NODE_COMPATIBILITY: Record< description: "Discord nodes typically send messages and updates to community channels", }, + gmail: { + canConnectTo: ["dataOutput"], + shouldConnectTo: ["dataOutput"], + commonPatterns: [ + "Process → Send email", + "Generate report → Email results", + "Data analysis → Email summary", + "Notifications → Email alerts", + ], + description: + "Gmail nodes typically send emails and notifications to recipients", + }, }; /**