-
Notifications
You must be signed in to change notification settings - Fork 1
feat: Add Email Trigger Node for IMAP email monitoring #26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
d46cb66
feat: Add Email Trigger Node for IMAP email monitoring
Justin322322 1e99d2e
fix: Resolve build and test issues for EmailTriggerNode
Justin322322 146b9c7
Fix CI failures: resolve module imports and EmailNode test issues
Justin322322 9e2f1f9
Fix build issue: prevent nodemailer from bundling on client side
Justin322322 68c6273
Merge branch 'main' into feature/email-trigger-node
Justin322322 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,181 +1,65 @@ | ||
| import { v4 as uuidv4 } from 'uuid' | ||
| import { EmailNodeConfig, EmailExecutionResult } from './EmailNode.types' | ||
| import { NodeExecutionContext, NodeExecutionResult } from '../types' | ||
| import { sendWithNodemailer, sendWithSendGrid } from './email-providers' | ||
| import { NodeExecutionContext } from '../types' | ||
|
|
||
| // Email service implementations | ||
| class EmailService { | ||
| static async sendEmail(config: EmailNodeConfig): Promise<EmailExecutionResult> { | ||
| const { emailService } = config | ||
|
|
||
| switch (emailService.type) { | ||
| case 'sendgrid': | ||
| return await sendWithSendGrid(config) | ||
| case 'gmail': | ||
| return await this.sendWithGmail(config) | ||
| case 'outlook': | ||
| return await this.sendWithOutlook(config) | ||
| case 'smtp': | ||
| return await this.sendWithSMTP(config) | ||
| default: | ||
| throw new Error(`Unsupported email service: ${emailService.type}`) | ||
| } | ||
| } | ||
| export async function executeEmailNode(context: NodeExecutionContext): Promise<{ success: boolean; output?: EmailExecutionResult; error?: string }> { | ||
| const { config, signal } = context | ||
|
|
||
| private static async sendWithGmail(config: EmailNodeConfig): Promise<EmailExecutionResult> { | ||
| // Gmail uses SMTP with specific settings | ||
| return await sendWithNodemailer({ | ||
| ...config, | ||
| emailService: { | ||
| ...config.emailService, | ||
| host: 'smtp.gmail.com', | ||
| port: 587, | ||
| secure: false | ||
| } | ||
| }, 'Gmail') | ||
| // Check for abort signal | ||
| if (signal?.aborted) { | ||
| return { success: false, error: 'Execution was cancelled' } | ||
| } | ||
|
|
||
| private static async sendWithOutlook(config: EmailNodeConfig): Promise<EmailExecutionResult> { | ||
| // Outlook uses SMTP with specific settings | ||
| return await sendWithNodemailer({ | ||
| ...config, | ||
| emailService: { | ||
| ...config.emailService, | ||
| host: 'smtp-mail.outlook.com', | ||
| port: 587, | ||
| secure: false | ||
| } | ||
| }, 'Outlook') | ||
| // Validate config | ||
| if (!config) { | ||
| return { success: false, error: 'Configuration is required' } | ||
| } | ||
|
|
||
| private static async sendWithSMTP(config: EmailNodeConfig): Promise<EmailExecutionResult> { | ||
| return await sendWithNodemailer(config, 'SMTP') | ||
| } | ||
| } | ||
| const emailConfig = config as EmailNodeConfig | ||
|
|
||
|
Comment on lines
+12
to
18
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Validate emailService and auth before execution Missing checks can yield runtime errors (e.g., accessing properties of undefined) or opaque provider failures. Add minimal guards. const emailConfig = config as EmailNodeConfig
// Validate required fields
if (!emailConfig.to || emailConfig.to.length === 0) {
return { success: false, error: 'At least one recipient is required' }
}
if (!emailConfig.subject || emailConfig.subject.trim() === '') {
return { success: false, error: 'Subject is required' }
}
if (!emailConfig.body || emailConfig.body.trim() === '') {
return { success: false, error: 'Email body is required' }
}
+ if (!emailConfig.emailService || !emailConfig.emailService.type) {
+ return { success: false, error: 'Email service configuration is required' }
+ }
+ if (emailConfig.emailService.type === 'sendgrid') {
+ if (!emailConfig.emailService.apiKey) {
+ return { success: false, error: 'SendGrid API key is required' }
+ }
+ } else {
+ if (!emailConfig.emailService.auth?.user || !emailConfig.emailService.auth?.pass) {
+ return { success: false, error: 'SMTP credentials (user/pass) are required' }
+ }
+ }Also applies to: 19-31 |
||
| export async function executeEmailNode(context: NodeExecutionContext): Promise<NodeExecutionResult> { | ||
| try { | ||
| const config = context.config as unknown as EmailNodeConfig | ||
|
|
||
| // Validate basic configuration | ||
| if (!Array.isArray(config.to) || config.to.length === 0) { | ||
| return { | ||
| success: false, | ||
| error: 'At least one recipient is required' | ||
| } | ||
| } | ||
|
|
||
| if (!config.subject || config.subject.trim().length === 0) { | ||
| return { | ||
| success: false, | ||
| error: 'Subject is required' | ||
| } | ||
| } | ||
|
|
||
| if (!config.body || config.body.trim().length === 0) { | ||
| return { | ||
| success: false, | ||
| error: 'Email body is required' | ||
| } | ||
| } | ||
|
|
||
| // Enhanced email service validation with security checks | ||
| if (!config.emailService) { | ||
| return { | ||
| success: false, | ||
| error: 'Email service configuration is required' | ||
| } | ||
| } | ||
| // Validate required fields | ||
| if (!emailConfig.to || emailConfig.to.length === 0) { | ||
| return { success: false, error: 'At least one recipient is required' } | ||
| } | ||
|
|
||
| // Validate service type | ||
| if (!config.emailService.type || !['smtp', 'gmail', 'outlook', 'sendgrid'].includes(config.emailService.type)) { | ||
| return { | ||
| success: false, | ||
| error: 'Valid email service type is required (smtp, gmail, outlook, sendgrid)' | ||
| } | ||
| } | ||
| if (!emailConfig.subject || emailConfig.subject.trim() === '') { | ||
| return { success: false, error: 'Subject is required' } | ||
| } | ||
|
|
||
| // Validate email address format | ||
| if (!config.emailService.auth?.user) { | ||
| return { | ||
| success: false, | ||
| error: 'Email address is required' | ||
| } | ||
| } | ||
| if (!emailConfig.body || emailConfig.body.trim() === '') { | ||
| return { success: false, error: 'Email body is required' } | ||
| } | ||
|
|
||
| const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ | ||
| if (!emailRegex.test(config.emailService.auth.user)) { | ||
| return { | ||
| success: false, | ||
| error: 'Valid email address format is required' | ||
| } | ||
| } | ||
| try { | ||
| let result: EmailExecutionResult | ||
|
|
||
| // For SendGrid, validate API key format | ||
| if (config.emailService.type === 'sendgrid') { | ||
| if (!config.emailService.apiKey) { | ||
| return { | ||
| success: false, | ||
| error: 'SendGrid API key is required' | ||
| } | ||
| } | ||
| if (!config.emailService.apiKey.startsWith('SG.')) { | ||
| return { | ||
| success: false, | ||
| error: 'SendGrid API key should start with "SG."' | ||
| } | ||
| // Only load email providers on the server side | ||
| if (typeof window === 'undefined') { | ||
| // Dynamic import to avoid bundling on client side | ||
| const { sendWithNodemailer, sendWithSendGrid } = await import('./email-providers') | ||
|
|
||
| if (emailConfig.emailService.type === 'sendgrid') { | ||
| result = await sendWithSendGrid(emailConfig) | ||
| } else { | ||
| // Default to nodemailer for SMTP/Gmail/Outlook | ||
| result = await sendWithNodemailer(emailConfig, emailConfig.emailService.type) | ||
| } | ||
| } else { | ||
| // For other services, validate password | ||
| if (!config.emailService.auth?.pass) { | ||
| return { | ||
| success: false, | ||
| error: 'Password or app-specific password is required' | ||
| } | ||
| } | ||
| if (config.emailService.auth.pass.length < 6) { | ||
| return { | ||
| success: false, | ||
| error: 'Password should be at least 6 characters long' | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Validate SMTP-specific settings | ||
| if (config.emailService.type === 'smtp') { | ||
| if (!config.emailService.host || config.emailService.host.trim().length === 0) { | ||
| return { | ||
| success: false, | ||
| error: 'SMTP host is required for SMTP service' | ||
| } | ||
| } | ||
| if (config.emailService.port && (config.emailService.port < 1 || config.emailService.port > 65535)) { | ||
| return { | ||
| success: false, | ||
| error: 'SMTP port must be between 1 and 65535' | ||
| } | ||
| // Client-side fallback - return simulated result | ||
| result = { | ||
| sent: true, | ||
| to: emailConfig.to, | ||
| subject: emailConfig.subject, | ||
| messageId: `client-${Date.now()}`, | ||
| timestamp: new Date(), | ||
| provider: `${emailConfig.emailService.type} (Client-side)` | ||
| } | ||
| } | ||
|
|
||
| // Check for abort signal | ||
| if (context.signal?.aborted) { | ||
| return { | ||
| success: false, | ||
| error: 'Execution was cancelled' | ||
| } | ||
| } | ||
|
|
||
| // Send real email using the configured service | ||
| const result = await EmailService.sendEmail(config) | ||
|
|
||
| return { | ||
| success: true, | ||
| output: result | ||
| } | ||
| return { success: true, output: result } | ||
| } catch (error) { | ||
| return { | ||
| success: false, | ||
| error: error instanceof Error ? error.message : 'Unknown error occurred' | ||
| return { | ||
| success: false, | ||
| error: error instanceof Error ? error.message : 'Unknown error occurred' | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Prevent potential crash when resolve.fallback is undefined
Guard against undefined and avoid spreading a non-object in older/edge configs.
if (!isServer) { // Exclude Node.js-specific packages from client-side bundling - config.resolve.fallback = { - ...config.resolve.fallback, + config.resolve = config.resolve || {} + config.resolve.fallback = { + ...(config.resolve.fallback ?? {}), net: false, tls: false, fs: false,📝 Committable suggestion
🤖 Prompt for AI Agents