Files
simstudioai--sim/apps/sim/triggers/imap/poller.ts
T
wehub-resource-sync d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

261 lines
8.1 KiB
TypeScript

import { createLogger } from '@sim/logger'
import { MailServerIcon } from '@/components/icons'
import { requestJson } from '@/lib/api/client/request'
import { imapMailboxesContract } from '@/lib/api/contracts/tools/imap'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import type { TriggerConfig } from '@/triggers/types'
const logger = createLogger('ImapPollingTrigger')
export const imapPollingTrigger: TriggerConfig = {
id: 'imap_poller',
name: 'IMAP Email Trigger',
provider: 'imap',
description: 'Triggers when new emails are received via IMAP (works with any email provider)',
version: '1.0.0',
icon: MailServerIcon,
polling: true,
subBlocks: [
// Connection settings
{
id: 'host',
title: 'IMAP Server',
type: 'short-input',
placeholder: 'imap.example.com',
description: 'IMAP server hostname (e.g., imap.gmail.com, outlook.office365.com)',
required: true,
mode: 'trigger',
},
{
id: 'port',
title: 'Port',
type: 'short-input',
placeholder: '993',
description: 'IMAP port (993 for SSL/TLS, 143 for STARTTLS)',
defaultValue: '993',
required: true,
mode: 'trigger',
},
{
id: 'secure',
title: 'Use SSL/TLS',
type: 'switch',
defaultValue: true,
description: 'Enable SSL/TLS encryption (recommended for port 993)',
required: false,
mode: 'trigger',
},
// Authentication
{
id: 'username',
title: 'Username',
type: 'short-input',
placeholder: 'user@example.com',
description: 'Email address or username for authentication',
required: true,
mode: 'trigger',
},
{
id: 'password',
title: 'Password',
type: 'short-input',
password: true,
placeholder: 'App password or email password',
description: 'Password or app-specific password (for Gmail, use an App Password)',
required: true,
mode: 'trigger',
},
// Mailbox selection
{
id: 'mailbox',
title: 'Mailboxes to Monitor',
type: 'dropdown',
multiSelect: true,
placeholder: 'Select mailboxes to monitor',
description:
'Choose which mailbox/folder(s) to monitor for new emails. Leave empty to monitor INBOX.',
required: false,
options: [],
fetchOptions: async (blockId: string) => {
const store = useSubBlockStore.getState()
const host = store.getValue(blockId, 'host') as string | null
const port = store.getValue(blockId, 'port') as string | null
const secure = store.getValue(blockId, 'secure') as boolean | null
const username = store.getValue(blockId, 'username') as string | null
const password = store.getValue(blockId, 'password') as string | null
if (!host || !username || !password) {
throw new Error('Please enter IMAP server, username, and password first')
}
try {
const data = await requestJson(imapMailboxesContract, {
body: {
host,
port: port ?? undefined,
secure: secure ?? undefined,
username,
password,
},
})
return data.mailboxes.map((mailbox) => ({
id: mailbox.path,
label: mailbox.name,
}))
} catch (error) {
logger.error('Error fetching IMAP mailboxes:', error)
throw error
}
},
dependsOn: ['host', 'port', 'secure', 'username', 'password'],
mode: 'trigger',
},
// Email filtering
{
id: 'searchCriteria',
title: 'Search Criteria',
type: 'code',
placeholder: '{ "unseen": true }',
description: 'ImapFlow search criteria as JSON object. Default: unseen messages only.',
defaultValue: '{ "unseen": true }',
required: false,
mode: 'trigger',
wandConfig: {
enabled: true,
maintainHistory: true,
generationType: 'json-object',
prompt: `Generate ImapFlow search criteria as a JSON object based on the user's description.
Available properties (all are optional, combine as needed):
- "unseen": true - Unread messages
- "seen": true - Read messages
- "flagged": true - Starred/flagged messages
- "answered": true - Replied messages
- "deleted": true - Deleted messages
- "draft": true - Draft messages
- "from": "sender@example.com" - From address contains
- "to": "recipient@example.com" - To address contains
- "cc": "cc@example.com" - CC address contains
- "subject": "keyword" - Subject contains
- "body": "text" - Body contains
- "text": "search" - Headers or body contains
- "since": "2024-01-01" - Emails since date (ISO format)
- "before": "2024-12-31" - Emails before date
- "larger": 10240 - Larger than N bytes
- "smaller": 1048576 - Smaller than N bytes
- "header": { "X-Priority": "1" } - Custom header search
- "or": [{ "from": "a@x.com" }, { "from": "b@x.com" }] - OR conditions
- "not": { "from": "spam@x.com" } - Negate condition
Multiple properties are combined with AND.
Examples:
- Unread from boss: { "unseen": true, "from": "boss@company.com" }
- From Alice or Bob: { "or": [{ "from": "alice@x.com" }, { "from": "bob@x.com" }] }
- Recent with keyword: { "since": "2024-01-01", "subject": "report" }
- Exclude spam: { "unseen": true, "not": { "from": "newsletter@x.com" } }
Current criteria: {context}
Return ONLY valid JSON, no explanations or markdown.`,
placeholder: 'Describe what emails you want to filter...',
},
},
// Processing options
{
id: 'markAsRead',
title: 'Mark as Read',
type: 'switch',
defaultValue: true,
description: 'Automatically mark emails as read (SEEN) after processing',
required: false,
mode: 'trigger',
},
{
id: 'includeAttachments',
title: 'Include Attachments',
type: 'switch',
defaultValue: false,
description: 'Download and include email attachments in the trigger payload',
required: false,
mode: 'trigger',
},
// Instructions
{
id: 'triggerInstructions',
title: 'Setup Instructions',
hideFromPreview: true,
type: 'text',
defaultValue: [
'Enter your IMAP server details (host, port, SSL settings)',
'Enter your email credentials (username and password)',
'For Gmail: Use an <a href="https://support.google.com/accounts/answer/185833" target="_blank">App Password</a> instead of your regular password',
'Select the mailbox to monitor (INBOX is most common)',
'Optionally configure search criteria and processing options',
'The system will automatically check for new emails and trigger your workflow',
]
.map(
(instruction, index) =>
`<div class="mb-3"><strong>${index + 1}.</strong> ${instruction}</div>`
)
.join(''),
mode: 'trigger',
},
],
outputs: {
email: {
messageId: {
type: 'string',
description: 'RFC Message-ID header',
},
subject: {
type: 'string',
description: 'Email subject line',
},
from: {
type: 'string',
description: 'Sender email address',
},
to: {
type: 'string',
description: 'Recipient email address',
},
cc: {
type: 'string',
description: 'CC recipients',
},
date: {
type: 'string',
description: 'Email date in ISO format',
},
bodyText: {
type: 'string',
description: 'Plain text email body',
},
bodyHtml: {
type: 'string',
description: 'HTML email body',
},
mailbox: {
type: 'string',
description: 'Mailbox/folder where email was received',
},
hasAttachments: {
type: 'boolean',
description: 'Whether email has attachments',
},
attachments: {
type: 'file[]',
description: 'Array of email attachments as files (if includeAttachments is enabled)',
},
},
timestamp: {
type: 'string',
description: 'Event timestamp',
},
},
}