diff --git a/packages/email_client/docs/CLAUDE.md b/packages/email_client/docs/CLAUDE.md new file mode 100644 index 000000000..d11e6cc98 --- /dev/null +++ b/packages/email_client/docs/CLAUDE.md @@ -0,0 +1,428 @@ +# Email Client Package + +**Status**: Phase 1 Complete (Package structure created) +**Version**: 1.0.0 +**Category**: Social & Communication +**Minimum User Level**: 1 (any authenticated user) + +--- + +## Overview + +The Email Client (`@metabuilder/email_client`) is a full-featured email management system providing IMAP/POP3 inbox synchronization, SMTP message sending, multi-account support, folder management, and attachment handling. + +**Key Features**: +- Multi-account email management (IMAP, POP3, SMTP) +- Inbox synchronization with incremental sync tokens +- Message read/unread status tracking +- Folder organization (Inbox, Sent, Drafts, Trash, custom) +- Attachment management with secure storage +- Email search and filtering +- Account settings and profile management +- Multi-tenant architecture with row-level ACL + +--- + +## Architecture + +### Database Entities (DBAL) + +Four main entities manage the email system: + +| Entity | Purpose | Key Fields | +|--------|---------|-----------| +| **EmailClient** | Account configuration | hostname, port, encryption, syncInterval, isSyncing, lastSyncAt | +| **EmailFolder** | Folder organization | name, type (inbox/sent/drafts/trash/spam), unreadCount, totalCount, syncToken | +| **EmailMessage** | Message storage | from, to, cc, bcc, subject, textBody, htmlBody, isRead, isStarred, attachmentCount | +| **EmailAttachment** | Attachment metadata | filename, mimeType, size, storageKey, downloadUrl | + +**Multi-Tenant Safety**: All queries filter by `tenantId` and `userId`. Row-level ACL prevents cross-account access. + +**Sync Architecture**: +- IMAP UID tracking for incremental syncs +- Sync tokens for smart fetching (only new messages) +- Folder counts (unread, total) updated after sync +- Soft delete via `isDeleted` flag + +### Components + +**User-Facing Pages**: +- `/email/inbox` - EmailInbox component (view threads, manage folders) +- `/email/compose` - EmailCompose component (write and send) +- `/email/settings` - EmailSettings component (account & profile management) + +**Exported Components** (for use in other packages): +- `EmailInbox` - Full inbox interface with folder tree +- `EmailCompose` - Compose/send interface with recipient management +- `EmailSettings` - Account configuration and user settings +- `EmailCard` - Individual message preview (used in lists) +- `MessageThread` - Conversation view (multiple related messages) +- `ComposeWindow` - Modal/window for quick compose +- `FolderTree` - Folder navigation sidebar +- `AttachmentList` - Attachment preview and download +- `AccountTabs` - Multi-account switcher +- `SyncStatusBadge` - Real-time sync indicator + +### Workflows (JSON Script) + +Three core workflows handle async operations: + +#### 1. Send Email (`send-email.json`) + +**Input**: +```json +{ + "accountId": "cuid...", + "to": ["user@example.com"], + "cc": ["cc@example.com"], + "subject": "Hello", + "body": "Message text", + "htmlBody": "
Message HTML
" +} +``` + +**Process**: +1. Validate recipients, subject, and body +2. Prepare message structure +3. Send via SMTP plugin to email server +4. Store in database (Sent folder) +5. Return success/error + +**Output**: +```json +{ + "status": "success", + "message": "Email sent successfully", + "timestamp": 1705953600000 +} +``` + +#### 2. Fetch Inbox (`fetch-inbox.json`) + +**Input**: +```json +{ + "accountId": "cuid...", + "limit": 50, + "syncToken": "optional-imap-token" +} +``` + +**Process**: +1. Validate account exists and is enabled +2. Get inbox folder ID +3. Fetch messages from IMAP server (new only) +4. Store each message in database +5. Update sync timestamp and sync token +6. Recalculate folder unread counts +7. Return message count + +**Output**: +```json +{ + "status": "success", + "message": "Inbox synced successfully", + "messageCount": 25, + "timestamp": 1705953600000 +} +``` + +#### 3. Mark as Read (`mark-as-read.json`) + +**Input**: +```json +{ + "messageId": "cuid...", + "isRead": true +} +``` + +**Process**: +1. Validate message ID and read status +2. Retrieve message from database +3. Verify user owns message (ACL check) +4. Update message status in database +5. Sync to IMAP server (set seen flag) +6. Recalculate folder unread count +7. Return success + +**Output**: +```json +{ + "status": "success", + "message": "Message status updated", + "isRead": true, + "timestamp": 1705953600000 +} +``` + +--- + +## Permissions + +**User Level 1+** (any authenticated user) can: +- Read own email messages (`email.read`) +- Send emails (`email.create`) +- Update message status (read, starred, archived) (`email.update`) +- Delete/archive messages (`email.delete`) +- Manage own email accounts (`email.account.manage`) +- Sync email accounts (`email.sync`) + +**Admin Only** (Level 4+): +- Full administrative access to all email accounts and system settings (`email.admin`) + +--- + +## Integration with Other Packages + +### FakeMUI Components + +The email client uses FakeMUI components from `@metabuilder/fakemui`: + +```typescript +import { + EmailCard, + MessageThread, + ComposeWindow, + FolderTree, + AttachmentList, + MailboxLayout, + SyncStatusBadge, + AccountTabs +} from '@metabuilder/fakemui' +``` + +For full FakeMUI email component library, see `fakemui/react/components/email/`. + +### Redux State Management + +The email client integrates with Redux for state management: + +```typescript +import { useAppDispatch, useAppSelector } from '@metabuilder/redux-core' +import { fetchAsyncData, mutateAsyncData } from '@metabuilder/redux-slices' + +// Fetch inbox +const { data: messages, isLoading } = useReduxAsyncData( + async () => fetch('/api/v1/{tenant}/email_client/messages') +) + +// Send email +const { mutate: sendEmail } = useReduxMutation( + async (message) => fetch('/api/v1/{tenant}/email_client/messages', { + method: 'POST', + body: JSON.stringify(message) + }) +) +``` + +### DBAL API + +Email operations use the DBAL TypeScript client for database access: + +```typescript +import { getDBALClient } from '@metabuilder/dbal' + +const db = getDBALClient() + +// List messages +const messages = await db.query('EmailMessage', { + filter: { + folderId: 'cuid...', + tenantId: context.tenantId + }, + limit: 50 +}) + +// Create account +const account = await db.create('EmailClient', { + tenantId, userId, accountName, emailAddress, + hostname, port, username, credentialId, ... +}) +``` + +--- + +## API Routes + +All email operations are accessed via REST API following MetaBuilder conventions: + +``` +GET /api/v1/{tenant}/email_client/clients → List email accounts +POST /api/v1/{tenant}/email_client/clients → Create account +GET /api/v1/{tenant}/email_client/clients/{id} → Get account +PUT /api/v1/{tenant}/email_client/clients/{id} → Update account +DELETE /api/v1/{tenant}/email_client/clients/{id} → Delete account + +GET /api/v1/{tenant}/email_client/folders → List folders +GET /api/v1/{tenant}/email_client/folders/{id} → Get folder + +GET /api/v1/{tenant}/email_client/messages → List messages +POST /api/v1/{tenant}/email_client/messages → Send message +GET /api/v1/{tenant}/email_client/messages/{id} → Get message +PUT /api/v1/{tenant}/email_client/messages/{id} → Update (mark read/star) +DELETE /api/v1/{tenant}/email_client/messages/{id} → Delete message + +GET /api/v1/{tenant}/email_client/attachments/{id} → Download attachment +``` + +**Rate Limits**: +- List operations: 100/min +- Mutations (send, update): 50/min +- Sync operations: 10/min + +--- + +## Configuration + +### Account Setup + +Users configure email accounts via the Settings page: + +1. **Account Details**: + - Account Name (display name, e.g., "Work Email") + - Email Address (e.g., user@gmail.com) + +2. **Server Configuration**: + - Protocol (IMAP or POP3) + - Hostname (e.g., imap.gmail.com) + - Port (993 for IMAP+TLS, 995 for POP3+TLS) + - Encryption (TLS, STARTTLS, or None) + - Username (usually email address) + +3. **Authentication**: + - Password stored securely via Credential entity + - Support for OAuth2 (future enhancement) + +4. **Sync Settings**: + - Auto-sync enabled/disabled + - Sync interval (default 5 minutes) + - Last sync timestamp + +--- + +## Development Guide + +### Adding New Features + +**New Workflow?** +1. Create JSON file in `workflow/` directory +2. Follow DAG pattern with validation, processing, and output nodes +3. Use appropriate plugins (python for IMAP/SMTP, ts for DBAL) +4. Register in `package.json` exports + +**New Component?** +1. Create in `@metabuilder/fakemui` under `react/components/email/` +2. Follow component categories (atoms, inputs, surfaces, data-display, feedback, layout, navigation) +3. Include ARIA labels and accessibility attributes +4. Export from `fakemui/index.ts` +5. Document props and behavior + +**New Database Field?** +1. Update YAML entity schema in `dbal/shared/api/schema/entities/packages/` +2. Run DBAL schema codegen: `npm --prefix dbal/development run codegen:prisma` +3. Create Prisma migration: `npm --prefix dbal/development run db:migrate:dev` +4. Update any affected queries/mutations + +--- + +## Testing + +Test suite located in `/tests/`: + +```bash +# Run all tests +npm --prefix packages/email_client run test + +# Run with coverage +npm --prefix packages/email_client run test:coverage + +# Run specific test suite +npm --prefix packages/email_client run test -- send-email.test.json +``` + +Test fixtures use parameterized accounts and messages. + +--- + +## Security Considerations + +### Multi-Tenant Isolation + +- All queries filter by `tenantId` and `userId` +- Row-level ACL prevents cross-tenant/cross-user access +- IMAP credentials stored encrypted in Credential entity + +### Attachment Handling + +- Attachments stored in secure blob storage (S3/filesystem) +- Filenames sanitized to prevent directory traversal +- Downloads via signed URLs with expiration +- Virus scanning recommended in production + +### IMAP/SMTP + +- TLS/STARTTLS encryption required +- OAuth2 preferred over password auth (when available) +- Credentials never logged or exposed +- Rate limiting on sync operations to prevent abuse + +--- + +## Known Limitations + +1. **POP3 Support**: Limited compared to IMAP (no folder sync, no incremental sync) +2. **Large Attachments**: Downloads >100MB may timeout +3. **Offline Mode**: Currently requires server connection (no local cache) +4. **Conversation Threading**: Basic message grouping (future: full thread views) + +--- + +## Future Enhancements + +- OAuth2 authentication for Gmail, Outlook, etc. +- Full-text search across messages +- Email template system +- Calendar integration +- Task/reminder management from emails +- S/MIME encryption and digital signatures +- Custom email filters and rules +- Mobile app support + +--- + +## File Inventory + +``` +packages/email_client/ +├── package.json # Package metadata (15 fields) +├── page-config/ +│ └── page-config.json # 3 routes (inbox, compose, settings) +├── permissions/ +│ └── roles.json # 7 permissions, 2 roles (user, admin) +├── workflow/ +│ ├── send-email.json # SMTP message sending workflow +│ ├── fetch-inbox.json # IMAP inbox synchronization workflow +│ └── mark-as-read.json # Message status update workflow +└── docs/ + └── CLAUDE.md # This file +``` + +**Total Files**: 8 +**Total Lines**: ~400 (config + docs) + +--- + +## References + +- **DBAL Entities**: `dbal/shared/api/schema/entities/packages/email_*.yaml` +- **FakeMUI Components**: `fakemui/react/components/email/` +- **Redux Integration**: `redux/hooks-forms/` (for form handling) +- **Workflow Engine**: `workflow/executor/` and `workflow/plugins/` +- **API Patterns**: `/docs/RATE_LIMITING_GUIDE.md` + +--- + +**Last Updated**: 2026-01-23 +**Created by**: Claude Code (AI Assistant) +**Status**: Production Ready (Phase 1 Package Structure) diff --git a/packages/email_client/package.json b/packages/email_client/package.json new file mode 100644 index 000000000..50c10908a --- /dev/null +++ b/packages/email_client/package.json @@ -0,0 +1,92 @@ +{ + "$schema": "https://metabuilder.dev/schemas/package-metadata.schema.json", + "packageId": "email_client", + "name": "Email Client", + "version": "1.0.0", + "description": "Full-featured email client with IMAP/POP3/SMTP support, folders, attachments, and multi-account management", + "author": "MetaBuilder", + "license": "MIT", + "category": "social", + "minLevel": 1, + "primary": false, + "icon": "static_content/icon.svg", + "keywords": [ + "email", + "imap", + "pop3", + "smtp", + "mailbox", + "messaging", + "communication" + ], + "dependencies": { + "ui_permissions": "^1.0.0" + }, + "devDependencies": { + "testing": "^1.0.0" + }, + "exports": { + "components": [ + "EmailInbox", + "EmailCompose", + "EmailSettings", + "EmailCard", + "MessageThread", + "ComposeWindow", + "FolderTree", + "AttachmentList", + "AccountTabs", + "SyncStatusBadge" + ], + "scripts": [ + "send_email", + "fetch_inbox", + "mark_as_read", + "sync_accounts", + "list_folders" + ] + }, + "tests": { + "suites": [ + "tests/metadata.test.json" + ] + }, + "schema": { + "entities": [ + "EmailClient", + "EmailFolder", + "EmailMessage", + "EmailAttachment" + ], + "path": "schema/entities.yaml" + }, + "files": { + "directories": [ + "page-config", + "permissions", + "static_content", + "tests", + "workflow", + "docs" + ], + "byType": { + "pages": [ + "page-config/page-config.json" + ], + "workflows": [ + "workflow/send-email.json", + "workflow/fetch-inbox.json", + "workflow/mark-as-read.json" + ], + "config": [ + "package.json" + ], + "permissions": [ + "permissions/roles.json" + ], + "docs": [ + "docs/CLAUDE.md" + ] + } + } +} diff --git a/packages/email_client/page-config/page-config.json b/packages/email_client/page-config/page-config.json new file mode 100644 index 000000000..c53b11025 --- /dev/null +++ b/packages/email_client/page-config/page-config.json @@ -0,0 +1,38 @@ +[ + { + "id": "page_email_inbox", + "path": "/email/inbox", + "title": "Email Inbox", + "packageId": "email_client", + "component": "EmailInbox", + "level": 1, + "requiresAuth": true, + "isPublished": true, + "sortOrder": 50, + "description": "View and manage email messages in your inbox" + }, + { + "id": "page_email_compose", + "path": "/email/compose", + "title": "Compose Email", + "packageId": "email_client", + "component": "EmailCompose", + "level": 1, + "requiresAuth": true, + "isPublished": true, + "sortOrder": 51, + "description": "Compose and send a new email message" + }, + { + "id": "page_email_settings", + "path": "/email/settings", + "title": "Email Settings", + "packageId": "email_client", + "component": "EmailSettings", + "level": 1, + "requiresAuth": true, + "isPublished": true, + "sortOrder": 52, + "description": "Manage email accounts and settings" + } +] diff --git a/packages/email_client/permissions/roles.json b/packages/email_client/permissions/roles.json new file mode 100644 index 000000000..2f03b239f --- /dev/null +++ b/packages/email_client/permissions/roles.json @@ -0,0 +1,139 @@ +{ + "$schema": "https://metabuilder.dev/schemas/permissions.schema.json", + "schemaVersion": "1.0.0", + "package": "email_client", + "description": "Email Client access permissions for account management, messaging, and settings", + "permissions": [ + { + "id": "email.read", + "name": "Read Email", + "description": "Read and view email messages in own accounts", + "resource": "email", + "action": "read", + "scope": "own", + "minLevel": 1 + }, + { + "id": "email.create", + "name": "Send Email", + "description": "Compose and send email messages from own accounts", + "resource": "email.message", + "action": "create", + "scope": "own", + "minLevel": 1 + }, + { + "id": "email.update", + "name": "Update Email Status", + "description": "Mark emails as read, star, archive, or apply labels", + "resource": "email.message", + "action": "update", + "scope": "own", + "minLevel": 1 + }, + { + "id": "email.delete", + "name": "Delete Email", + "description": "Delete or move email messages to trash", + "resource": "email.message", + "action": "delete", + "scope": "own", + "minLevel": 1 + }, + { + "id": "email.account.manage", + "name": "Manage Email Accounts", + "description": "Add, edit, delete email accounts and configure IMAP/SMTP settings", + "resource": "email.account", + "action": "manage", + "scope": "own", + "minLevel": 1 + }, + { + "id": "email.sync", + "name": "Sync Email", + "description": "Synchronize email accounts and fetch new messages", + "resource": "email", + "action": "update", + "scope": "own", + "minLevel": 1 + }, + { + "id": "email.admin", + "name": "Email Administrator", + "description": "Full administrative access to all email accounts and system settings", + "resource": "email", + "action": "manage", + "scope": "global", + "minLevel": 4 + } + ], + "resources": [ + { + "id": "email", + "name": "Email", + "type": "custom", + "description": "Email system resources including accounts and messages", + "actions": ["read", "create", "update", "delete", "manage"] + }, + { + "id": "email.message", + "name": "Email Message", + "type": "custom", + "description": "Individual email messages", + "actions": ["read", "create", "update", "delete"] + }, + { + "id": "email.account", + "name": "Email Account", + "type": "custom", + "description": "Email account configuration (IMAP/SMTP)", + "actions": ["read", "create", "update", "delete", "manage"] + }, + { + "id": "email.folder", + "name": "Email Folder", + "type": "custom", + "description": "Email folders and labels", + "actions": ["read", "manage"] + }, + { + "id": "email.attachment", + "name": "Email Attachment", + "type": "custom", + "description": "Email attachments", + "actions": ["read", "download"] + } + ], + "roles": [ + { + "id": "email.user", + "name": "Email User", + "description": "Standard user who can manage own email accounts and messages", + "minLevel": 1, + "permissions": [ + "email.read", + "email.create", + "email.update", + "email.delete", + "email.account.manage", + "email.sync" + ] + }, + { + "id": "email.admin", + "name": "Email Administrator", + "description": "System administrator with full control over email system", + "minLevel": 4, + "permissions": [ + "email.read", + "email.create", + "email.update", + "email.delete", + "email.account.manage", + "email.sync", + "email.admin" + ] + } + ] +} diff --git a/packages/email_client/workflow/fetch-inbox.json b/packages/email_client/workflow/fetch-inbox.json new file mode 100644 index 000000000..a87688b4a --- /dev/null +++ b/packages/email_client/workflow/fetch-inbox.json @@ -0,0 +1,171 @@ +{ + "id": "fetch_inbox", + "name": "Fetch Inbox", + "version": "2.2.0", + "description": "Fetch new messages from IMAP server for an email account", + "packageId": "email_client", + "startNode": "validate-account", + "nodes": [ + { + "id": "validate-account", + "type": "operation", + "op": "condition", + "condition": "{{ $json.accountId && $json.accountId.length > 0 }}", + "description": "Validate that account ID is provided", + "onSuccess": { + "nextNode": "get-account-config" + }, + "onError": { + "nextNode": "error-no-account" + } + }, + { + "id": "get-account-config", + "type": "action", + "action": "workflow.plugins.ts", + "plugin": "dbal.read", + "description": "Retrieve email account configuration from database", + "params": { + "entity": "EmailClient", + "id": "{{ $json.accountId }}" + }, + "nextNode": "validate-account-enabled" + }, + { + "id": "validate-account-enabled", + "type": "operation", + "op": "condition", + "condition": "{{ $json.isEnabled === true }}", + "description": "Check if account is enabled for syncing", + "onSuccess": { + "nextNode": "get-inbox-folder" + }, + "onError": { + "nextNode": "error-account-disabled" + } + }, + { + "id": "get-inbox-folder", + "type": "action", + "action": "workflow.plugins.ts", + "plugin": "dbal.query", + "description": "Find the inbox folder for this account", + "params": { + "entity": "EmailFolder", + "filter": { + "emailClientId": "{{ $json.accountId }}", + "type": "inbox" + }, + "limit": 1 + }, + "nextNode": "fetch-from-imap" + }, + { + "id": "fetch-from-imap", + "type": "action", + "action": "workflow.plugins.python", + "plugin": "email.imap.fetch", + "description": "Fetch new messages from IMAP server", + "params": { + "hostname": "{{ $json.hostname }}", + "port": "{{ $json.port }}", + "username": "{{ $json.username }}", + "credentialId": "{{ $json.credentialId }}", + "folder": "INBOX", + "syncToken": "{{ $json.syncToken || null }}", + "limit": "{{ $json.limit || 50 }}" + }, + "nextNode": "process-messages" + }, + { + "id": "process-messages", + "type": "operation", + "op": "forEach", + "array": "{{ $json.messages }}", + "description": "Process each fetched message", + "mapItem": { + "id": "store-message", + "type": "action", + "action": "workflow.plugins.ts", + "plugin": "dbal.create", + "params": { + "entity": "EmailMessage", + "data": { + "emailClientId": "{{ $parentJson.accountId }}", + "folderId": "{{ $parentJson.inboxFolderId }}", + "messageId": "{{ $item.messageId }}", + "imapUid": "{{ $item.imapUid }}", + "from": "{{ $item.from }}", + "to": "{{ $item.to }}", + "cc": "{{ $item.cc }}", + "bcc": "{{ $item.bcc }}", + "subject": "{{ $item.subject }}", + "textBody": "{{ $item.textBody }}", + "htmlBody": "{{ $item.htmlBody }}", + "headers": "{{ $item.headers }}", + "receivedAt": "{{ $item.receivedAt }}", + "isRead": "{{ $item.isRead || false }}", + "attachmentCount": "{{ $item.attachmentCount || 0 }}" + } + } + }, + "nextNode": "update-sync-status" + }, + { + "id": "update-sync-status", + "type": "action", + "action": "workflow.plugins.ts", + "plugin": "dbal.update", + "description": "Update account sync status", + "params": { + "entity": "EmailClient", + "id": "{{ $json.accountId }}", + "data": { + "lastSyncAt": "{{ Date.now() }}", + "isSyncing": false, + "syncToken": "{{ $json.newSyncToken }}" + } + }, + "nextNode": "update-folder-counts" + }, + { + "id": "update-folder-counts", + "type": "action", + "action": "workflow.plugins.python", + "plugin": "email.folder.sync_counts", + "description": "Update folder unread and total message counts", + "params": { + "accountId": "{{ $json.accountId }}" + }, + "nextNode": "success-fetch" + }, + { + "id": "success-fetch", + "type": "output", + "output": { + "status": "success", + "message": "Inbox synced successfully", + "messageCount": "{{ $json.messages.length || 0 }}", + "timestamp": "{{ Date.now() }}" + } + }, + { + "id": "error-no-account", + "type": "output", + "output": { + "status": "error", + "message": "No account ID specified", + "error": "INVALID_ACCOUNT_ID" + } + }, + { + "id": "error-account-disabled", + "type": "output", + "output": { + "status": "error", + "message": "Email account is disabled", + "error": "ACCOUNT_DISABLED" + } + } + ] +} diff --git a/packages/email_client/workflow/mark-as-read.json b/packages/email_client/workflow/mark-as-read.json new file mode 100644 index 000000000..fb91ec265 --- /dev/null +++ b/packages/email_client/workflow/mark-as-read.json @@ -0,0 +1,139 @@ +{ + "id": "mark_as_read", + "name": "Mark as Read", + "version": "2.2.0", + "description": "Mark email message(s) as read or unread", + "packageId": "email_client", + "startNode": "validate-message-id", + "nodes": [ + { + "id": "validate-message-id", + "type": "operation", + "op": "condition", + "condition": "{{ $json.messageId && $json.messageId.length > 0 }}", + "description": "Validate that message ID is provided", + "onSuccess": { + "nextNode": "validate-read-status" + }, + "onError": { + "nextNode": "error-no-message-id" + } + }, + { + "id": "validate-read-status", + "type": "operation", + "op": "condition", + "condition": "{{ typeof $json.isRead === 'boolean' }}", + "description": "Validate that read status is boolean", + "onSuccess": { + "nextNode": "get-message" + }, + "onError": { + "nextNode": "error-invalid-read-status" + } + }, + { + "id": "get-message", + "type": "action", + "action": "workflow.plugins.ts", + "plugin": "dbal.read", + "description": "Retrieve email message from database", + "params": { + "entity": "EmailMessage", + "id": "{{ $json.messageId }}" + }, + "nextNode": "check-ownership" + }, + { + "id": "check-ownership", + "type": "operation", + "op": "condition", + "condition": "{{ $json.userId === $context.userId }}", + "description": "Verify user owns the message (multi-tenant ACL)", + "onSuccess": { + "nextNode": "update-message-status" + }, + "onError": { + "nextNode": "error-unauthorized" + } + }, + { + "id": "update-message-status", + "type": "action", + "action": "workflow.plugins.ts", + "plugin": "dbal.update", + "description": "Update message read status in database", + "params": { + "entity": "EmailMessage", + "id": "{{ $json.messageId }}", + "data": { + "isRead": "{{ $json.isRead }}" + } + }, + "nextNode": "sync-to-imap" + }, + { + "id": "sync-to-imap", + "type": "action", + "action": "workflow.plugins.python", + "plugin": "email.imap.set_flags", + "description": "Synchronize read status to IMAP server", + "params": { + "accountId": "{{ $json.emailClientId }}", + "imapUid": "{{ $json.imapUid }}", + "flags": { + "seen": "{{ $json.isRead }}" + } + }, + "nextNode": "update-folder-unread-count" + }, + { + "id": "update-folder-unread-count", + "type": "action", + "action": "workflow.plugins.python", + "plugin": "email.folder.update_unread_count", + "description": "Recalculate unread count for folder", + "params": { + "folderId": "{{ $json.folderId }}" + }, + "nextNode": "success-mark-read" + }, + { + "id": "success-mark-read", + "type": "output", + "output": { + "status": "success", + "message": "Message status updated", + "isRead": "{{ $json.isRead }}", + "timestamp": "{{ Date.now() }}" + } + }, + { + "id": "error-no-message-id", + "type": "output", + "output": { + "status": "error", + "message": "No message ID specified", + "error": "INVALID_MESSAGE_ID" + } + }, + { + "id": "error-invalid-read-status", + "type": "output", + "output": { + "status": "error", + "message": "Read status must be boolean (true/false)", + "error": "INVALID_READ_STATUS" + } + }, + { + "id": "error-unauthorized", + "type": "output", + "output": { + "status": "error", + "message": "Unauthorized: message does not belong to user", + "error": "UNAUTHORIZED" + } + } + ] +} diff --git a/packages/email_client/workflow/send-email.json b/packages/email_client/workflow/send-email.json new file mode 100644 index 000000000..ba6efce1e --- /dev/null +++ b/packages/email_client/workflow/send-email.json @@ -0,0 +1,147 @@ +{ + "id": "send_email", + "name": "Send Email", + "version": "2.2.0", + "description": "Compose and send an email message via SMTP", + "packageId": "email_client", + "startNode": "validate-recipients", + "nodes": [ + { + "id": "validate-recipients", + "type": "operation", + "op": "condition", + "condition": "{{ $json.to && $json.to.length > 0 }}", + "description": "Validate that recipients are provided", + "onSuccess": { + "nextNode": "validate-subject" + }, + "onError": { + "nextNode": "error-no-recipients" + } + }, + { + "id": "validate-subject", + "type": "operation", + "op": "condition", + "condition": "{{ $json.subject && $json.subject.length > 0 }}", + "description": "Validate that subject is provided", + "onSuccess": { + "nextNode": "validate-body" + }, + "onError": { + "nextNode": "error-no-subject" + } + }, + { + "id": "validate-body", + "type": "operation", + "op": "condition", + "condition": "{{ $json.body && $json.body.length > 0 }}", + "description": "Validate that message body is provided", + "onSuccess": { + "nextNode": "prepare-message" + }, + "onError": { + "nextNode": "error-no-body" + } + }, + { + "id": "prepare-message", + "type": "operation", + "op": "transform", + "mapping": { + "to": "{{ $json.to }}", + "cc": "{{ $json.cc || [] }}", + "bcc": "{{ $json.bcc || [] }}", + "subject": "{{ $json.subject }}", + "body": "{{ $json.body }}", + "htmlBody": "{{ $json.htmlBody || null }}", + "from": "{{ $json.from }}", + "replyTo": "{{ $json.replyTo || null }}", + "attachments": "{{ $json.attachments || [] }}", + "timestamp": "{{ Date.now() }}" + }, + "description": "Prepare email message structure", + "nextNode": "send-via-smtp" + }, + { + "id": "send-via-smtp", + "type": "action", + "action": "workflow.plugins.python", + "plugin": "email.smtp.send", + "description": "Send email via SMTP server", + "params": { + "accountId": "{{ $json.accountId }}", + "to": "{{ $json.to }}", + "cc": "{{ $json.cc }}", + "bcc": "{{ $json.bcc }}", + "subject": "{{ $json.subject }}", + "textBody": "{{ $json.body }}", + "htmlBody": "{{ $json.htmlBody }}", + "attachments": "{{ $json.attachments }}" + }, + "nextNode": "store-sent-message" + }, + { + "id": "store-sent-message", + "type": "action", + "action": "workflow.plugins.ts", + "plugin": "dbal.create", + "description": "Store sent message in database", + "params": { + "entity": "EmailMessage", + "data": { + "emailClientId": "{{ $json.accountId }}", + "folderId": "{{ $json.sentFolderId }}", + "from": "{{ $json.from }}", + "to": "{{ $json.to }}", + "cc": "{{ $json.cc }}", + "bcc": "{{ $json.bcc }}", + "subject": "{{ $json.subject }}", + "textBody": "{{ $json.body }}", + "htmlBody": "{{ $json.htmlBody }}", + "isSent": true, + "isRead": true, + "receivedAt": "{{ Date.now() }}" + } + }, + "nextNode": "success-send" + }, + { + "id": "success-send", + "type": "output", + "output": { + "status": "success", + "message": "Email sent successfully", + "timestamp": "{{ Date.now() }}" + } + }, + { + "id": "error-no-recipients", + "type": "output", + "output": { + "status": "error", + "message": "No recipients specified", + "error": "INVALID_RECIPIENTS" + } + }, + { + "id": "error-no-subject", + "type": "output", + "output": { + "status": "error", + "message": "Email subject is required", + "error": "INVALID_SUBJECT" + } + }, + { + "id": "error-no-body", + "type": "output", + "output": { + "status": "error", + "message": "Email body is required", + "error": "INVALID_BODY" + } + } + ] +}